rotifex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1958 -0
- package/bin/rotifex.js +18 -0
- package/config.default.json +29 -0
- package/package.json +48 -0
- package/src/ai/agent.routes.js +110 -0
- package/src/ai/agent.service.js +326 -0
- package/src/ai/agents.config.js +66 -0
- package/src/ai/ai.config.js +84 -0
- package/src/ai/ai.routes.js +149 -0
- package/src/ai/ai.service.js +275 -0
- package/src/ai/ai.usage.js +58 -0
- package/src/ai/tools/registry.js +156 -0
- package/src/auth/auth.controller.js +118 -0
- package/src/auth/auth.routes.js +27 -0
- package/src/auth/auth.service.js +182 -0
- package/src/auth/jwt.middleware.js +41 -0
- package/src/auth/password.util.js +6 -0
- package/src/commands/init.js +30 -0
- package/src/commands/migrate.js +62 -0
- package/src/commands/start.js +96 -0
- package/src/db/adapters/base.js +73 -0
- package/src/db/adapters/sqlite.js +86 -0
- package/src/db/connection.js +45 -0
- package/src/db/index.js +12 -0
- package/src/db/migrator.js +142 -0
- package/src/db/schema.js +48 -0
- package/src/engine/index.js +76 -0
- package/src/engine/queryBuilder.js +52 -0
- package/src/engine/routeFactory.js +119 -0
- package/src/engine/schemaLoader.js +75 -0
- package/src/engine/schemaStore.js +35 -0
- package/src/engine/tableSync.js +28 -0
- package/src/engine/zodFactory.js +54 -0
- package/src/lib/config.js +120 -0
- package/src/lib/logBuffer.js +75 -0
- package/src/lib/logger.js +26 -0
- package/src/server/index.js +8 -0
- package/src/server/middleware/errorHandler.js +26 -0
- package/src/server/middleware/requestLogger.js +23 -0
- package/src/server/plugins.js +38 -0
- package/src/server/routes/admin.js +276 -0
- package/src/server/routes/files.js +188 -0
- package/src/server/routes/health.js +14 -0
- package/src/server/server.js +149 -0
- package/src/storage/fileTable.js +22 -0
- package/src/storage/index.js +5 -0
- package/src/storage/storageManager.js +225 -0
package/README.md
ADDED
|
@@ -0,0 +1,1958 @@
|
|
|
1
|
+
# Rotifex — Complete Documentation Source
|
|
2
|
+
|
|
3
|
+
> This file is the single source of truth for generating full product documentation.
|
|
4
|
+
> It is structured for compatibility with Docusaurus, Mintlify, GitBook, and similar systems.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
1. [Application Overview](#1-application-overview)
|
|
11
|
+
2. [Feature Documentation](#2-feature-documentation)
|
|
12
|
+
3. [API Documentation](#3-api-documentation)
|
|
13
|
+
4. [Authentication](#4-authentication)
|
|
14
|
+
5. [Models / Data Structures](#5-models--data-structures)
|
|
15
|
+
6. [AI / LLM Integration](#6-ai--llm-integration)
|
|
16
|
+
7. [File Storage / Media Handling](#7-file-storage--media-handling)
|
|
17
|
+
8. [Admin Panel Features](#8-admin-panel-features)
|
|
18
|
+
9. [Error Handling](#9-error-handling)
|
|
19
|
+
10. [Environment Configuration](#10-environment-configuration)
|
|
20
|
+
11. [Deployment](#11-deployment)
|
|
21
|
+
12. [Example Workflows](#12-example-workflows)
|
|
22
|
+
13. [Notes for Documentation Generators](#13-notes-for-documentation-generators)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 1. Application Overview
|
|
27
|
+
|
|
28
|
+
### Description
|
|
29
|
+
|
|
30
|
+
**Rotifex** is a self-hosted backend-as-a-service platform. It lets developers define data models through a JSON schema file or a visual admin panel, and instantly get a full REST API — no code generation required. It ships with JWT authentication, file storage, AI/LLM integration, agent execution, and a built-in admin dashboard.
|
|
31
|
+
|
|
32
|
+
### Core Purpose
|
|
33
|
+
|
|
34
|
+
Rotifex eliminates boilerplate for backend development. Instead of writing CRUD endpoints, migrations, and auth logic manually, developers define a schema and Rotifex handles everything: table creation, route registration, validation, pagination, filtering, and sorting — live, without restarts.
|
|
35
|
+
|
|
36
|
+
### Key Capabilities
|
|
37
|
+
|
|
38
|
+
- **Schema-driven REST API** — define a model, get five CRUD endpoints instantly
|
|
39
|
+
- **Live schema updates** — add or remove models at runtime without restarting the server
|
|
40
|
+
- **JWT Authentication** — register, login, refresh tokens, role-based access control
|
|
41
|
+
- **File storage** — upload, download, manage public and private files with signed URLs
|
|
42
|
+
- **AI/LLM integration** — connect OpenAI, Anthropic, Gemini, and Ollama; generate and chat
|
|
43
|
+
- **AI Agents** — ReAct-loop agents with tools: calculator, web search, HTTP GET, DB query, datetime
|
|
44
|
+
- **Token usage tracking** — persistent per-provider token consumption logged to disk
|
|
45
|
+
- **Admin dashboard** — full SPA for managing schemas, users, files, AI providers, agents, logs, and settings
|
|
46
|
+
- **Rate limiting, CORS, structured logging** — production-ready out of the box
|
|
47
|
+
|
|
48
|
+
### Target Users
|
|
49
|
+
|
|
50
|
+
- Solo developers who need a backend quickly
|
|
51
|
+
- Startups prototyping a product without a dedicated backend engineer
|
|
52
|
+
- Internal tools teams who need structured data storage with an admin interface
|
|
53
|
+
- Developers integrating LLMs into their applications
|
|
54
|
+
|
|
55
|
+
### Architecture Overview
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
+-----------------------------------------------------+
|
|
59
|
+
| Rotifex Server |
|
|
60
|
+
| +----------+ +------------+ +-----------------+ |
|
|
61
|
+
| | Fastify | | Schema | | SQLite (via | |
|
|
62
|
+
| | HTTP | | Engine | | better-sqlite3)| |
|
|
63
|
+
| | Server | | (live) | | | |
|
|
64
|
+
| +----------+ +------------+ +-----------------+ |
|
|
65
|
+
| +----------+ +------------+ +-----------------+ |
|
|
66
|
+
| | JWT | | Storage | | AI / Agents | |
|
|
67
|
+
| | Auth | | Manager | | System | |
|
|
68
|
+
| +----------+ +------------+ +-----------------+ |
|
|
69
|
+
| +------------------------------------------------+ |
|
|
70
|
+
| | Admin SPA (React + Vite) | |
|
|
71
|
+
| +------------------------------------------------+ |
|
|
72
|
+
+-----------------------------------------------------+
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- **Framework:** Fastify v5 (Node.js)
|
|
76
|
+
- **Database:** SQLite via `better-sqlite3`
|
|
77
|
+
- **Admin frontend:** React 19 + Vite, served as a static SPA from `/`
|
|
78
|
+
- **Config format:** JSON (`schema.json`, `ai.config.json`, `agents.config.json`)
|
|
79
|
+
- **Persistence:** `.env` for secrets, JSON files for AI/agent config, SQLite for all app data
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 2. Feature Documentation
|
|
84
|
+
|
|
85
|
+
### 2.1 Dynamic REST Engine
|
|
86
|
+
|
|
87
|
+
**Description:** The core of Rotifex. Reads `schema.json`, creates SQLite tables, and registers five CRUD routes per model — all at startup and live when models are added via the admin API.
|
|
88
|
+
|
|
89
|
+
**Use case:** A developer defines a `Product` model with `name`, `price`, and `in_stock` fields. Rotifex immediately exposes `/api/products` with full CRUD, filtering, sorting, and pagination — no restart needed.
|
|
90
|
+
|
|
91
|
+
**How it works:**
|
|
92
|
+
|
|
93
|
+
1. `schema.json` is parsed by `schemaLoader.js` into normalized model definitions.
|
|
94
|
+
2. `tableSync.js` runs `CREATE TABLE IF NOT EXISTS` for each model.
|
|
95
|
+
3. `routeFactory.js` registers generic parametric routes (`/api/:table`) that resolve the model from an in-memory store at request time.
|
|
96
|
+
4. When a model is added/removed via the admin API, the in-memory store is updated and routes resolve immediately.
|
|
97
|
+
|
|
98
|
+
**Field types supported:**
|
|
99
|
+
|
|
100
|
+
| Schema Type | SQLite Type | Notes |
|
|
101
|
+
| ----------- | ----------- | ------------- |
|
|
102
|
+
| `string` | `TEXT` | |
|
|
103
|
+
| `number` | `REAL` | Float |
|
|
104
|
+
| `integer` | `INTEGER` | |
|
|
105
|
+
| `boolean` | `INTEGER` | Stored as 0/1 |
|
|
106
|
+
|
|
107
|
+
**Field definition formats:**
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
// Shorthand
|
|
111
|
+
{ "name": "string" }
|
|
112
|
+
|
|
113
|
+
// Full form
|
|
114
|
+
{
|
|
115
|
+
"name": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"required": true,
|
|
118
|
+
"unique": false,
|
|
119
|
+
"default": "Unnamed"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Auto-generated columns:** Every model automatically gets `id` (UUID), `created_at` (ISO 8601), and `updated_at` (ISO 8601).
|
|
125
|
+
|
|
126
|
+
**Table naming:** Model names are lowercased and pluralized. `Product` -> table `products`, route `/api/products`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### 2.2 JWT Authentication
|
|
131
|
+
|
|
132
|
+
**Description:** Full authentication system with access tokens, refresh tokens, password hashing, and role-based access control.
|
|
133
|
+
|
|
134
|
+
**Use case:** Secure user registration and login for a Rotifex-backed application. Protect admin routes from regular users.
|
|
135
|
+
|
|
136
|
+
**How it works:**
|
|
137
|
+
|
|
138
|
+
1. `POST /auth/register` hashes the password with bcrypt and inserts a user row.
|
|
139
|
+
2. `POST /auth/login` verifies credentials and issues a short-lived access token (1 hour) and long-lived refresh token (30 days).
|
|
140
|
+
3. The JWT middleware runs on every request, verifies the `Authorization: Bearer` header, and injects `x-user-id` / `x-user-role` headers that downstream routes use for authorization.
|
|
141
|
+
4. `POST /auth/refresh` issues a new token pair without requiring the password.
|
|
142
|
+
|
|
143
|
+
**Roles:** `user` (default) and `admin`. Admin access is required for all `/admin/api/*` endpoints.
|
|
144
|
+
|
|
145
|
+
**Password rules:**
|
|
146
|
+
|
|
147
|
+
- Minimum 8 characters
|
|
148
|
+
- At least one letter
|
|
149
|
+
- At least one number
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### 2.3 File Storage
|
|
154
|
+
|
|
155
|
+
**Description:** Upload, download, list, and delete files. Supports public and private visibility with HMAC-signed URLs for private file access.
|
|
156
|
+
|
|
157
|
+
**Use case:** Allow users to upload profile pictures, documents, or any binary files. Private files can only be accessed by their uploader or admins, or via a time-limited signed URL.
|
|
158
|
+
|
|
159
|
+
**How it works:**
|
|
160
|
+
|
|
161
|
+
1. Files are uploaded via multipart form to `POST /files/upload`.
|
|
162
|
+
2. The `StorageManager` validates MIME type and per-user storage quota.
|
|
163
|
+
3. Files are stored on disk with UUID-based names in separate `public/` and `private/` directories.
|
|
164
|
+
4. Metadata (name, MIME type, size, uploader, visibility) is recorded in the `_files` SQLite table.
|
|
165
|
+
5. Private files require a signed URL generated by `GET /files/:id/signed-url`. The URL includes an HMAC token and expiry timestamp; it is verified on download.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### 2.4 AI / LLM Integration
|
|
170
|
+
|
|
171
|
+
**Description:** Connect multiple LLM providers and use them for text generation and multi-turn chat from a unified API.
|
|
172
|
+
|
|
173
|
+
**Use case:** Add AI-powered features to your application — content generation, Q&A, summarization — using whichever provider you have access to.
|
|
174
|
+
|
|
175
|
+
**How it works:**
|
|
176
|
+
|
|
177
|
+
1. Providers (OpenAI, Anthropic, Gemini, Ollama) are configured via the admin panel; API keys are stored in `ai.config.json`.
|
|
178
|
+
2. `POST /api/ai/generate` and `POST /api/ai/chat` proxy requests to the selected provider using native `fetch`.
|
|
179
|
+
3. Each call records token usage to `ai.usage.json` for persistent tracking.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### 2.5 AI Agents
|
|
184
|
+
|
|
185
|
+
**Description:** Configurable AI agents that use a ReAct (Reasoning + Acting) loop to complete multi-step tasks using tools.
|
|
186
|
+
|
|
187
|
+
**Use case:** An agent tasked with "What's 15% of the current Bitcoin price?" will search the web for the price, then use the calculator tool to compute the result — all automatically.
|
|
188
|
+
|
|
189
|
+
**How it works:**
|
|
190
|
+
|
|
191
|
+
1. Agents are defined with a name, provider, model, system prompt, tool list, temperature, and iteration limits.
|
|
192
|
+
2. When run, the agent sends the task to the LLM with tool definitions.
|
|
193
|
+
3. If the LLM calls a tool, the tool is executed and the result is appended to the conversation.
|
|
194
|
+
4. The loop continues until the LLM returns a final answer or `maxIterations` is reached.
|
|
195
|
+
5. Steps (thinking, tool call, tool result, final answer) are returned for inspection.
|
|
196
|
+
|
|
197
|
+
**Available tools:**
|
|
198
|
+
|
|
199
|
+
| Tool | Description |
|
|
200
|
+
| ---------------- | ------------------------------------------------------------------------ |
|
|
201
|
+
| `get_datetime` | Returns current ISO 8601 datetime, UTC string, and Unix timestamp |
|
|
202
|
+
| `calculate` | Safely evaluates math expressions (`+`, `-`, `*`, `/`, `%`, parentheses) |
|
|
203
|
+
| `web_search` | DuckDuckGo instant-answer search (no API key required) |
|
|
204
|
+
| `http_get` | Makes HTTP GET requests to public URLs; returns up to 4000 chars of body |
|
|
205
|
+
| `database_query` | Runs read-only `SELECT` queries on the Rotifex SQLite database |
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### 2.6 Admin Dashboard
|
|
210
|
+
|
|
211
|
+
**Description:** A React SPA served at `/` providing a visual interface for all administrative operations.
|
|
212
|
+
|
|
213
|
+
**Tabs:**
|
|
214
|
+
|
|
215
|
+
- **Dashboard** — stat cards (schemas, records, users, files, storage, connected LLMs, agents, uptime, status) and overview tables
|
|
216
|
+
- **Database Schemas** — create, view, and delete data models
|
|
217
|
+
- **User Management** — view and manage registered users
|
|
218
|
+
- **File Browser** — browse, preview, and delete uploaded files
|
|
219
|
+
- **AI Integration** — configure providers, playground for generate/chat, agent management, API docs
|
|
220
|
+
- **Server Logs** — real-time in-memory log viewer with level filtering
|
|
221
|
+
- **Settings** — edit environment variables (port, CORS, rate limits, JWT secrets, storage limits)
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 3. API Documentation
|
|
226
|
+
|
|
227
|
+
### Base URL
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
http://localhost:3000
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
All API responses follow the envelope format:
|
|
234
|
+
|
|
235
|
+
- **Success:** `{ "data": <payload>, "meta"?: <pagination> }`
|
|
236
|
+
- **Error:** `{ "error": "<type>", "message": "<detail>", "statusCode": <number> }`
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
### 3.1 Health
|
|
241
|
+
|
|
242
|
+
#### `GET /health`
|
|
243
|
+
|
|
244
|
+
Check server status.
|
|
245
|
+
|
|
246
|
+
**Auth:** None
|
|
247
|
+
|
|
248
|
+
**Response:**
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"status": "ok",
|
|
253
|
+
"uptime": 3600.5,
|
|
254
|
+
"timestamp": "2026-03-06T12:00:00.000Z"
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### 3.2 Authentication Endpoints
|
|
261
|
+
|
|
262
|
+
#### `POST /auth/register`
|
|
263
|
+
|
|
264
|
+
Register a new user.
|
|
265
|
+
|
|
266
|
+
**Auth:** None
|
|
267
|
+
|
|
268
|
+
**Request Body:**
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"email": "user@example.com",
|
|
273
|
+
"password": "secret123",
|
|
274
|
+
"display_name": "Jane Doe",
|
|
275
|
+
"role": "user"
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
| Field | Type | Required | Notes |
|
|
280
|
+
| -------------- | ------ | -------- | ------------------------------------------- |
|
|
281
|
+
| `email` | string | Yes | Must be a valid email |
|
|
282
|
+
| `password` | string | Yes | Min 8 chars, 1 letter, 1 number |
|
|
283
|
+
| `display_name` | string | No | Display name |
|
|
284
|
+
| `role` | string | No | `"user"` or `"admin"`. Defaults to `"user"` |
|
|
285
|
+
|
|
286
|
+
**Response `201`:**
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"data": {
|
|
291
|
+
"id": "uuid",
|
|
292
|
+
"email": "user@example.com",
|
|
293
|
+
"display_name": "Jane Doe",
|
|
294
|
+
"role": "user",
|
|
295
|
+
"created_at": "2026-03-06T12:00:00.000Z"
|
|
296
|
+
},
|
|
297
|
+
"message": "User registered successfully"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Errors:**
|
|
302
|
+
|
|
303
|
+
| Code | Reason |
|
|
304
|
+
| ----- | ------------------------------------------------ |
|
|
305
|
+
| `400` | Validation failed (invalid email, weak password) |
|
|
306
|
+
| `409` | Email already in use |
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
#### `POST /auth/login`
|
|
311
|
+
|
|
312
|
+
Authenticate and receive tokens.
|
|
313
|
+
|
|
314
|
+
**Auth:** None
|
|
315
|
+
|
|
316
|
+
**Request Body:**
|
|
317
|
+
|
|
318
|
+
```json
|
|
319
|
+
{
|
|
320
|
+
"email": "user@example.com",
|
|
321
|
+
"password": "secret123"
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Response `200`:**
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"data": {
|
|
330
|
+
"accessToken": "<jwt>",
|
|
331
|
+
"refreshToken": "<jwt>",
|
|
332
|
+
"user": {
|
|
333
|
+
"id": "uuid",
|
|
334
|
+
"email": "user@example.com",
|
|
335
|
+
"display_name": "Jane Doe",
|
|
336
|
+
"role": "user"
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Errors:**
|
|
343
|
+
|
|
344
|
+
| Code | Reason |
|
|
345
|
+
| ----- | ------------------------- |
|
|
346
|
+
| `400` | Missing email or password |
|
|
347
|
+
| `401` | Invalid credentials |
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
#### `POST /auth/refresh`
|
|
352
|
+
|
|
353
|
+
Exchange a refresh token for a new token pair.
|
|
354
|
+
|
|
355
|
+
**Auth:** None
|
|
356
|
+
|
|
357
|
+
**Request Body:**
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
{
|
|
361
|
+
"refreshToken": "<jwt>"
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Response `200`:**
|
|
366
|
+
|
|
367
|
+
```json
|
|
368
|
+
{
|
|
369
|
+
"data": {
|
|
370
|
+
"accessToken": "<new-jwt>",
|
|
371
|
+
"refreshToken": "<new-jwt>"
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Errors:**
|
|
377
|
+
|
|
378
|
+
| Code | Reason |
|
|
379
|
+
| ----- | -------------------------------- |
|
|
380
|
+
| `400` | Missing refreshToken |
|
|
381
|
+
| `401` | Invalid or expired refresh token |
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
#### `GET /auth/me`
|
|
386
|
+
|
|
387
|
+
Return the currently authenticated user.
|
|
388
|
+
|
|
389
|
+
**Auth:** `Authorization: Bearer <accessToken>`
|
|
390
|
+
|
|
391
|
+
**Response `200`:**
|
|
392
|
+
|
|
393
|
+
```json
|
|
394
|
+
{
|
|
395
|
+
"data": {
|
|
396
|
+
"id": "uuid",
|
|
397
|
+
"email": "user@example.com",
|
|
398
|
+
"display_name": "Jane Doe",
|
|
399
|
+
"role": "user",
|
|
400
|
+
"created_at": "2026-03-06T12:00:00.000Z"
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Errors:**
|
|
406
|
+
|
|
407
|
+
| Code | Reason |
|
|
408
|
+
| ----- | ------------------------ |
|
|
409
|
+
| `401` | Missing or invalid token |
|
|
410
|
+
| `404` | User not found |
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
### 3.3 Dynamic CRUD Endpoints
|
|
415
|
+
|
|
416
|
+
These endpoints are available for every model defined in `schema.json`. Replace `:table` with the model's table name (e.g. `products` for a `Product` model).
|
|
417
|
+
|
|
418
|
+
#### `GET /api/:table`
|
|
419
|
+
|
|
420
|
+
List records with filtering, sorting, and pagination.
|
|
421
|
+
|
|
422
|
+
**Auth:** Optional
|
|
423
|
+
|
|
424
|
+
**Query Parameters:**
|
|
425
|
+
|
|
426
|
+
| Parameter | Type | Default | Description |
|
|
427
|
+
| --------- | ------- | ------------ | ---------------------------------------- |
|
|
428
|
+
| `page` | integer | `1` | Page number |
|
|
429
|
+
| `limit` | integer | `20` | Records per page (max 100) |
|
|
430
|
+
| `sort` | string | `created_at` | Field to sort by |
|
|
431
|
+
| `order` | string | `DESC` | `ASC` or `DESC` |
|
|
432
|
+
| `<field>` | any | — | Filter by exact value on any model field |
|
|
433
|
+
|
|
434
|
+
**Example:** `GET /api/products?sort=price&order=ASC&page=2&limit=10&in_stock=1`
|
|
435
|
+
|
|
436
|
+
**Response `200`:**
|
|
437
|
+
|
|
438
|
+
```json
|
|
439
|
+
{
|
|
440
|
+
"data": [
|
|
441
|
+
{
|
|
442
|
+
"id": "uuid",
|
|
443
|
+
"name": "Widget",
|
|
444
|
+
"price": 9.99,
|
|
445
|
+
"in_stock": 1,
|
|
446
|
+
"created_at": "2026-03-06T12:00:00.000Z",
|
|
447
|
+
"updated_at": "2026-03-06T12:00:00.000Z"
|
|
448
|
+
}
|
|
449
|
+
],
|
|
450
|
+
"meta": {
|
|
451
|
+
"total": 42,
|
|
452
|
+
"page": 2,
|
|
453
|
+
"limit": 10,
|
|
454
|
+
"pages": 5
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
#### `GET /api/:table/:id`
|
|
462
|
+
|
|
463
|
+
Get a single record by ID.
|
|
464
|
+
|
|
465
|
+
**Response `200`:**
|
|
466
|
+
|
|
467
|
+
```json
|
|
468
|
+
{
|
|
469
|
+
"data": {
|
|
470
|
+
"id": "uuid",
|
|
471
|
+
"name": "Widget",
|
|
472
|
+
"price": 9.99
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**Errors:**
|
|
478
|
+
|
|
479
|
+
| Code | Reason |
|
|
480
|
+
| ----- | --------------------------------- |
|
|
481
|
+
| `404` | Record not found or unknown table |
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
#### `POST /api/:table`
|
|
486
|
+
|
|
487
|
+
Create a new record.
|
|
488
|
+
|
|
489
|
+
**Auth:** Optional
|
|
490
|
+
|
|
491
|
+
**Request Body:** JSON object matching the model's field definitions.
|
|
492
|
+
|
|
493
|
+
```json
|
|
494
|
+
{
|
|
495
|
+
"name": "Widget",
|
|
496
|
+
"price": 9.99,
|
|
497
|
+
"in_stock": true
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Response `201`:**
|
|
502
|
+
|
|
503
|
+
```json
|
|
504
|
+
{
|
|
505
|
+
"data": {
|
|
506
|
+
"id": "uuid",
|
|
507
|
+
"name": "Widget",
|
|
508
|
+
"price": 9.99,
|
|
509
|
+
"in_stock": 1,
|
|
510
|
+
"created_at": "2026-03-06T12:00:00.000Z",
|
|
511
|
+
"updated_at": "2026-03-06T12:00:00.000Z"
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Errors:**
|
|
517
|
+
|
|
518
|
+
| Code | Reason |
|
|
519
|
+
| ----- | ------------------------------------------------------- |
|
|
520
|
+
| `400` | Validation error (missing required fields, wrong types) |
|
|
521
|
+
| `404` | Unknown table |
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
#### `PUT /api/:table/:id`
|
|
526
|
+
|
|
527
|
+
Update a record (partial — only send fields to change).
|
|
528
|
+
|
|
529
|
+
**Request Body:**
|
|
530
|
+
|
|
531
|
+
```json
|
|
532
|
+
{
|
|
533
|
+
"price": 14.99
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
**Response `200`:**
|
|
538
|
+
|
|
539
|
+
```json
|
|
540
|
+
{
|
|
541
|
+
"data": {
|
|
542
|
+
"id": "uuid",
|
|
543
|
+
"name": "Widget",
|
|
544
|
+
"price": 14.99
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
**Errors:**
|
|
550
|
+
|
|
551
|
+
| Code | Reason |
|
|
552
|
+
| ----- | -------------------------------------- |
|
|
553
|
+
| `400` | Validation error or no fields provided |
|
|
554
|
+
| `404` | Record or table not found |
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
#### `DELETE /api/:table/:id`
|
|
559
|
+
|
|
560
|
+
Delete a record.
|
|
561
|
+
|
|
562
|
+
**Response:** `204 No Content`
|
|
563
|
+
|
|
564
|
+
**Errors:**
|
|
565
|
+
|
|
566
|
+
| Code | Reason |
|
|
567
|
+
| ----- | ------------------------- |
|
|
568
|
+
| `404` | Record or table not found |
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
### 3.4 File Endpoints
|
|
573
|
+
|
|
574
|
+
#### `POST /files/upload`
|
|
575
|
+
|
|
576
|
+
Upload a file.
|
|
577
|
+
|
|
578
|
+
**Auth:** Optional (identity from JWT `Authorization` header)
|
|
579
|
+
|
|
580
|
+
**Request:** `multipart/form-data`
|
|
581
|
+
|
|
582
|
+
| Field | Type | Required | Description |
|
|
583
|
+
| ------------ | ------ | -------- | ----------------------------------- |
|
|
584
|
+
| `file` | file | Yes | The file to upload |
|
|
585
|
+
| `visibility` | string | No | `"public"` (default) or `"private"` |
|
|
586
|
+
|
|
587
|
+
**Response `201`:**
|
|
588
|
+
|
|
589
|
+
```json
|
|
590
|
+
{
|
|
591
|
+
"data": {
|
|
592
|
+
"id": "uuid",
|
|
593
|
+
"original_name": "photo.jpg",
|
|
594
|
+
"stored_name": "uuid.jpg",
|
|
595
|
+
"mime_type": "image/jpeg",
|
|
596
|
+
"size_bytes": 204800,
|
|
597
|
+
"visibility": "public",
|
|
598
|
+
"uploader_id": "user-uuid",
|
|
599
|
+
"created_at": "2026-03-06T12:00:00.000Z"
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Errors:**
|
|
605
|
+
|
|
606
|
+
| Code | Reason |
|
|
607
|
+
| ----- | ----------------------------------------- |
|
|
608
|
+
| `400` | No file provided, invalid visibility |
|
|
609
|
+
| `413` | File exceeds size limit or per-user quota |
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
#### `GET /files`
|
|
614
|
+
|
|
615
|
+
List files. Admins see all files; other users see only their own.
|
|
616
|
+
|
|
617
|
+
**Response `200`:**
|
|
618
|
+
|
|
619
|
+
```json
|
|
620
|
+
{
|
|
621
|
+
"data": [
|
|
622
|
+
/* array of file metadata objects */
|
|
623
|
+
],
|
|
624
|
+
"meta": { "total": 5 }
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
#### `GET /files/:id`
|
|
631
|
+
|
|
632
|
+
Get metadata for a single file.
|
|
633
|
+
|
|
634
|
+
**Errors:**
|
|
635
|
+
|
|
636
|
+
| Code | Reason |
|
|
637
|
+
| ----- | --------------------------- |
|
|
638
|
+
| `403` | Not the owner and not admin |
|
|
639
|
+
| `404` | File not found |
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
#### `GET /files/:id/download`
|
|
644
|
+
|
|
645
|
+
Download a file. Public files are accessible directly. Private files require a signed URL.
|
|
646
|
+
|
|
647
|
+
**Query Parameters (private files only):**
|
|
648
|
+
|
|
649
|
+
| Parameter | Description |
|
|
650
|
+
| --------- | ---------------------------------- |
|
|
651
|
+
| `token` | HMAC token from the signed URL |
|
|
652
|
+
| `expires` | Unix timestamp from the signed URL |
|
|
653
|
+
|
|
654
|
+
**Response:** File stream with appropriate `Content-Type` and `Content-Disposition` headers.
|
|
655
|
+
|
|
656
|
+
**Errors:**
|
|
657
|
+
|
|
658
|
+
| Code | Reason |
|
|
659
|
+
| ----- | ---------------------------------------------- |
|
|
660
|
+
| `403` | Private file accessed without valid signed URL |
|
|
661
|
+
| `404` | File not found |
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
#### `GET /files/:id/signed-url`
|
|
666
|
+
|
|
667
|
+
Generate a time-limited signed URL for a private file.
|
|
668
|
+
|
|
669
|
+
**Auth:** Must be the file owner or admin.
|
|
670
|
+
|
|
671
|
+
**Response `200`:**
|
|
672
|
+
|
|
673
|
+
```json
|
|
674
|
+
{
|
|
675
|
+
"data": {
|
|
676
|
+
"url": "http://localhost:3000/files/uuid/download?token=abc123&expires=1741300000",
|
|
677
|
+
"expires": 1741300000
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**Errors:**
|
|
683
|
+
|
|
684
|
+
| Code | Reason |
|
|
685
|
+
| ----- | --------------------------- |
|
|
686
|
+
| `400` | File is not private |
|
|
687
|
+
| `403` | Not the owner and not admin |
|
|
688
|
+
| `404` | File not found |
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
#### `DELETE /files/:id`
|
|
693
|
+
|
|
694
|
+
Delete a file from disk and database.
|
|
695
|
+
|
|
696
|
+
**Auth:** Must be the file owner or admin.
|
|
697
|
+
|
|
698
|
+
**Response:** `204 No Content`
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
### 3.5 AI Endpoints
|
|
703
|
+
|
|
704
|
+
#### `GET /api/ai/providers`
|
|
705
|
+
|
|
706
|
+
List all enabled AI providers (public).
|
|
707
|
+
|
|
708
|
+
**Response `200`:**
|
|
709
|
+
|
|
710
|
+
```json
|
|
711
|
+
{
|
|
712
|
+
"data": [
|
|
713
|
+
{
|
|
714
|
+
"id": "openai",
|
|
715
|
+
"label": "OpenAI",
|
|
716
|
+
"models": ["gpt-4o", "gpt-4o-mini"],
|
|
717
|
+
"defaultModel": "gpt-4o"
|
|
718
|
+
}
|
|
719
|
+
]
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
#### `GET /api/ai/models`
|
|
726
|
+
|
|
727
|
+
Flat list of all models across all enabled providers.
|
|
728
|
+
|
|
729
|
+
**Response `200`:**
|
|
730
|
+
|
|
731
|
+
```json
|
|
732
|
+
{
|
|
733
|
+
"data": [
|
|
734
|
+
{ "provider": "openai", "providerLabel": "OpenAI", "model": "gpt-4o" },
|
|
735
|
+
{
|
|
736
|
+
"provider": "anthropic",
|
|
737
|
+
"providerLabel": "Anthropic",
|
|
738
|
+
"model": "claude-sonnet-4-6"
|
|
739
|
+
}
|
|
740
|
+
]
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
#### `GET /api/ai/models/:provider`
|
|
747
|
+
|
|
748
|
+
Models for a specific provider.
|
|
749
|
+
|
|
750
|
+
**Response `200`:**
|
|
751
|
+
|
|
752
|
+
```json
|
|
753
|
+
{ "data": ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"] }
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
---
|
|
757
|
+
|
|
758
|
+
#### `POST /api/ai/generate`
|
|
759
|
+
|
|
760
|
+
Generate a single text completion.
|
|
761
|
+
|
|
762
|
+
**Request Body:**
|
|
763
|
+
|
|
764
|
+
```json
|
|
765
|
+
{
|
|
766
|
+
"provider": "openai",
|
|
767
|
+
"model": "gpt-4o",
|
|
768
|
+
"prompt": "Explain quantum entanglement in one sentence.",
|
|
769
|
+
"system": "You are a physics professor.",
|
|
770
|
+
"maxTokens": 256,
|
|
771
|
+
"temperature": 0.7
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
| Field | Type | Required | Description |
|
|
776
|
+
| ------------- | ------- | -------- | ------------------------------------------------------- |
|
|
777
|
+
| `provider` | string | Yes | Provider ID (`openai`, `anthropic`, `gemini`, `ollama`) |
|
|
778
|
+
| `prompt` | string | Yes | The user prompt |
|
|
779
|
+
| `model` | string | No | Defaults to provider's `defaultModel` |
|
|
780
|
+
| `system` | string | No | System instruction |
|
|
781
|
+
| `maxTokens` | integer | No | Maximum tokens to generate |
|
|
782
|
+
| `temperature` | number | No | Sampling temperature (0-2) |
|
|
783
|
+
|
|
784
|
+
**Response `200`:**
|
|
785
|
+
|
|
786
|
+
```json
|
|
787
|
+
{
|
|
788
|
+
"data": {
|
|
789
|
+
"text": "Quantum entanglement is a phenomenon where two particles...",
|
|
790
|
+
"model": "gpt-4o",
|
|
791
|
+
"usage": {
|
|
792
|
+
"prompt_tokens": 42,
|
|
793
|
+
"completion_tokens": 28
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
#### `POST /api/ai/chat`
|
|
802
|
+
|
|
803
|
+
Multi-turn conversation.
|
|
804
|
+
|
|
805
|
+
**Request Body:**
|
|
806
|
+
|
|
807
|
+
```json
|
|
808
|
+
{
|
|
809
|
+
"provider": "anthropic",
|
|
810
|
+
"model": "claude-sonnet-4-6",
|
|
811
|
+
"messages": [
|
|
812
|
+
{ "role": "user", "content": "Hello!" },
|
|
813
|
+
{ "role": "assistant", "content": "Hi there! How can I help?" },
|
|
814
|
+
{ "role": "user", "content": "What is 2+2?" }
|
|
815
|
+
],
|
|
816
|
+
"system": "You are a helpful assistant.",
|
|
817
|
+
"maxTokens": 512
|
|
818
|
+
}
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
**Response `200`:**
|
|
822
|
+
|
|
823
|
+
```json
|
|
824
|
+
{
|
|
825
|
+
"data": {
|
|
826
|
+
"message": { "role": "assistant", "content": "2+2 equals 4." },
|
|
827
|
+
"model": "claude-sonnet-4-6",
|
|
828
|
+
"usage": {
|
|
829
|
+
"prompt_tokens": 60,
|
|
830
|
+
"completion_tokens": 12
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
---
|
|
837
|
+
|
|
838
|
+
### 3.6 Agent Endpoints
|
|
839
|
+
|
|
840
|
+
#### `GET /api/agents`
|
|
841
|
+
|
|
842
|
+
List all defined agents (public).
|
|
843
|
+
|
|
844
|
+
**Response `200`:**
|
|
845
|
+
|
|
846
|
+
```json
|
|
847
|
+
{
|
|
848
|
+
"data": [
|
|
849
|
+
{
|
|
850
|
+
"id": "uuid",
|
|
851
|
+
"name": "Research Assistant",
|
|
852
|
+
"description": "Searches the web and summarizes results",
|
|
853
|
+
"provider": "openai",
|
|
854
|
+
"model": "gpt-4o",
|
|
855
|
+
"tools": ["web_search", "calculate"],
|
|
856
|
+
"createdAt": "2026-03-06T12:00:00.000Z"
|
|
857
|
+
}
|
|
858
|
+
]
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
#### `GET /api/agents/tools`
|
|
865
|
+
|
|
866
|
+
List all available tools.
|
|
867
|
+
|
|
868
|
+
**Response `200`:**
|
|
869
|
+
|
|
870
|
+
```json
|
|
871
|
+
{
|
|
872
|
+
"data": [
|
|
873
|
+
{
|
|
874
|
+
"name": "calculate",
|
|
875
|
+
"description": "Safely evaluate a mathematical expression...",
|
|
876
|
+
"parameters": {
|
|
877
|
+
"expression": { "type": "string", "description": "A math expression" }
|
|
878
|
+
},
|
|
879
|
+
"required": ["expression"]
|
|
880
|
+
}
|
|
881
|
+
]
|
|
882
|
+
}
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
---
|
|
886
|
+
|
|
887
|
+
#### `POST /api/agents/:id/run`
|
|
888
|
+
|
|
889
|
+
Run an agent with a task input.
|
|
890
|
+
|
|
891
|
+
**Request Body:**
|
|
892
|
+
|
|
893
|
+
```json
|
|
894
|
+
{
|
|
895
|
+
"input": "What is 18% tip on a $47.50 bill?"
|
|
896
|
+
}
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
**Response `200`:**
|
|
900
|
+
|
|
901
|
+
```json
|
|
902
|
+
{
|
|
903
|
+
"data": {
|
|
904
|
+
"agentId": "uuid",
|
|
905
|
+
"agentName": "Math Helper",
|
|
906
|
+
"input": "What is 18% tip on a $47.50 bill?",
|
|
907
|
+
"output": "The 18% tip on a $47.50 bill is $8.55.",
|
|
908
|
+
"steps": [
|
|
909
|
+
{
|
|
910
|
+
"type": "thinking",
|
|
911
|
+
"content": "I need to calculate 18% of 47.50",
|
|
912
|
+
"iteration": 1
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
"type": "tool_call",
|
|
916
|
+
"tool": "calculate",
|
|
917
|
+
"args": { "expression": "47.50 * 0.18" },
|
|
918
|
+
"iteration": 1
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
"type": "tool_result",
|
|
922
|
+
"tool": "calculate",
|
|
923
|
+
"result": "{\"expression\":\"47.50 * 0.18\",\"result\":8.55}",
|
|
924
|
+
"iteration": 1
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
"type": "final_answer",
|
|
928
|
+
"content": "The 18% tip on a $47.50 bill is $8.55.",
|
|
929
|
+
"iteration": 2
|
|
930
|
+
}
|
|
931
|
+
],
|
|
932
|
+
"usage": {
|
|
933
|
+
"prompt_tokens": 320,
|
|
934
|
+
"completion_tokens": 85
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
**Errors:**
|
|
941
|
+
|
|
942
|
+
| Code | Reason |
|
|
943
|
+
| ----- | ------------------------ |
|
|
944
|
+
| `400` | Missing or empty `input` |
|
|
945
|
+
| `404` | Agent not found |
|
|
946
|
+
| `500` | LLM provider error |
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
### 3.7 Admin Endpoints
|
|
951
|
+
|
|
952
|
+
All admin endpoints require the `x-user-role: admin` header (automatically injected from JWT when role is `admin`).
|
|
953
|
+
|
|
954
|
+
#### `GET /admin/api/stats`
|
|
955
|
+
|
|
956
|
+
Dashboard statistics.
|
|
957
|
+
|
|
958
|
+
**Response `200`:**
|
|
959
|
+
|
|
960
|
+
```json
|
|
961
|
+
{
|
|
962
|
+
"data": {
|
|
963
|
+
"models": [{ "model": "Product", "table": "products", "count": 42 }],
|
|
964
|
+
"users": { "count": 15 },
|
|
965
|
+
"files": { "count": 8, "storageMB": 12.5 },
|
|
966
|
+
"uptime": 3600,
|
|
967
|
+
"ai": {
|
|
968
|
+
"connectedLLMs": 2,
|
|
969
|
+
"enabledLLMs": 2,
|
|
970
|
+
"providers": [{ "id": "openai", "label": "OpenAI", "hasKey": true }],
|
|
971
|
+
"agentsCount": 3,
|
|
972
|
+
"usage": {
|
|
973
|
+
"totalRequests": 120,
|
|
974
|
+
"totalInputTokens": 45000,
|
|
975
|
+
"totalOutputTokens": 12000,
|
|
976
|
+
"byProvider": {
|
|
977
|
+
"openai": {
|
|
978
|
+
"requests": 80,
|
|
979
|
+
"inputTokens": 30000,
|
|
980
|
+
"outputTokens": 8000
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
991
|
+
#### `GET /admin/api/schema`
|
|
992
|
+
|
|
993
|
+
Get all model definitions.
|
|
994
|
+
|
|
995
|
+
**Response `200`:**
|
|
996
|
+
|
|
997
|
+
```json
|
|
998
|
+
{
|
|
999
|
+
"data": {
|
|
1000
|
+
"Product": {
|
|
1001
|
+
"tableName": "products",
|
|
1002
|
+
"fields": [
|
|
1003
|
+
{ "name": "name", "type": "string", "required": true },
|
|
1004
|
+
{ "name": "price", "type": "number", "required": false }
|
|
1005
|
+
]
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
---
|
|
1012
|
+
|
|
1013
|
+
#### `POST /admin/api/schema`
|
|
1014
|
+
|
|
1015
|
+
Create a new model. Routes become active immediately — no restart required.
|
|
1016
|
+
|
|
1017
|
+
**Request Body:**
|
|
1018
|
+
|
|
1019
|
+
```json
|
|
1020
|
+
{
|
|
1021
|
+
"name": "Product",
|
|
1022
|
+
"fields": {
|
|
1023
|
+
"name": { "type": "string", "required": true },
|
|
1024
|
+
"price": "number",
|
|
1025
|
+
"in_stock": "boolean"
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
**Response `201`:**
|
|
1031
|
+
|
|
1032
|
+
```json
|
|
1033
|
+
{
|
|
1034
|
+
"data": { "name": "Product", "tableName": "products", "fields": [] },
|
|
1035
|
+
"message": "Model \"Product\" is live. Routes /products are active now."
|
|
1036
|
+
}
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
**Errors:**
|
|
1040
|
+
|
|
1041
|
+
| Code | Reason |
|
|
1042
|
+
| ----- | ---------------------------------------------- |
|
|
1043
|
+
| `400` | Missing `name` or `fields`, reserved name used |
|
|
1044
|
+
| `409` | Model with that name already exists |
|
|
1045
|
+
|
|
1046
|
+
---
|
|
1047
|
+
|
|
1048
|
+
#### `DELETE /admin/api/schema/:name`
|
|
1049
|
+
|
|
1050
|
+
Remove a model. Routes are deactivated immediately; the DB table is preserved.
|
|
1051
|
+
|
|
1052
|
+
**Response:** `204 No Content`
|
|
1053
|
+
|
|
1054
|
+
**Errors:**
|
|
1055
|
+
|
|
1056
|
+
| Code | Reason |
|
|
1057
|
+
| ----- | ---------------------------------------------- |
|
|
1058
|
+
| `400` | Attempt to delete a system model (e.g. `User`) |
|
|
1059
|
+
| `404` | Model not found |
|
|
1060
|
+
|
|
1061
|
+
---
|
|
1062
|
+
|
|
1063
|
+
#### `GET /admin/api/logs`
|
|
1064
|
+
|
|
1065
|
+
Retrieve in-memory server logs.
|
|
1066
|
+
|
|
1067
|
+
**Query Parameters:**
|
|
1068
|
+
|
|
1069
|
+
| Parameter | Description |
|
|
1070
|
+
| --------- | ------------------------------------------------- |
|
|
1071
|
+
| `after` | Unix timestamp — only return logs after this time |
|
|
1072
|
+
| `level` | Filter by level: `info`, `warn`, `error`, `debug` |
|
|
1073
|
+
|
|
1074
|
+
**Response `200`:**
|
|
1075
|
+
|
|
1076
|
+
```json
|
|
1077
|
+
{
|
|
1078
|
+
"logs": [
|
|
1079
|
+
{
|
|
1080
|
+
"ts": 1741258800000,
|
|
1081
|
+
"level": "info",
|
|
1082
|
+
"msg": "GET /api/products 200 12ms"
|
|
1083
|
+
}
|
|
1084
|
+
]
|
|
1085
|
+
}
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
---
|
|
1089
|
+
|
|
1090
|
+
#### `GET /admin/api/env`
|
|
1091
|
+
|
|
1092
|
+
Read current environment/config values.
|
|
1093
|
+
|
|
1094
|
+
**Response `200`:**
|
|
1095
|
+
|
|
1096
|
+
```json
|
|
1097
|
+
{
|
|
1098
|
+
"data": {
|
|
1099
|
+
"JWT_SECRET": "***",
|
|
1100
|
+
"ROTIFEX_PORT": "3000",
|
|
1101
|
+
"ROTIFEX_CORS_ORIGIN": "*"
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
---
|
|
1107
|
+
|
|
1108
|
+
#### `POST /admin/api/env`
|
|
1109
|
+
|
|
1110
|
+
Write environment variables to `.env`. Restart required for changes to take effect.
|
|
1111
|
+
|
|
1112
|
+
**Request Body:**
|
|
1113
|
+
|
|
1114
|
+
```json
|
|
1115
|
+
{
|
|
1116
|
+
"vars": {
|
|
1117
|
+
"ROTIFEX_PORT": "4000",
|
|
1118
|
+
"ROTIFEX_CORS_ORIGIN": "https://myapp.com"
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
**Response `200`:**
|
|
1124
|
+
|
|
1125
|
+
```json
|
|
1126
|
+
{
|
|
1127
|
+
"message": "Environment saved. Restart the server for changes to take effect."
|
|
1128
|
+
}
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
---
|
|
1132
|
+
|
|
1133
|
+
#### `GET /admin/api/ai/providers`
|
|
1134
|
+
|
|
1135
|
+
Get all AI providers with masked API keys.
|
|
1136
|
+
|
|
1137
|
+
**Response `200`:**
|
|
1138
|
+
|
|
1139
|
+
```json
|
|
1140
|
+
{
|
|
1141
|
+
"data": [
|
|
1142
|
+
{
|
|
1143
|
+
"id": "openai",
|
|
1144
|
+
"label": "OpenAI",
|
|
1145
|
+
"enabled": true,
|
|
1146
|
+
"apiKey": "***abcd",
|
|
1147
|
+
"hasKey": true,
|
|
1148
|
+
"models": ["gpt-4o", "gpt-4o-mini"],
|
|
1149
|
+
"defaultModel": "gpt-4o"
|
|
1150
|
+
}
|
|
1151
|
+
]
|
|
1152
|
+
}
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
---
|
|
1156
|
+
|
|
1157
|
+
#### `PUT /admin/api/ai/providers/:id`
|
|
1158
|
+
|
|
1159
|
+
Update a provider's configuration.
|
|
1160
|
+
|
|
1161
|
+
**Request Body:**
|
|
1162
|
+
|
|
1163
|
+
```json
|
|
1164
|
+
{
|
|
1165
|
+
"enabled": true,
|
|
1166
|
+
"apiKey": "sk-...",
|
|
1167
|
+
"defaultModel": "gpt-4o-mini"
|
|
1168
|
+
}
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
**Response `200`:**
|
|
1172
|
+
|
|
1173
|
+
```json
|
|
1174
|
+
{
|
|
1175
|
+
"data": {
|
|
1176
|
+
"id": "openai",
|
|
1177
|
+
"label": "OpenAI",
|
|
1178
|
+
"enabled": true,
|
|
1179
|
+
"hasKey": true
|
|
1180
|
+
},
|
|
1181
|
+
"message": "Provider \"openai\" updated."
|
|
1182
|
+
}
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
---
|
|
1186
|
+
|
|
1187
|
+
#### `GET /admin/api/agents`
|
|
1188
|
+
|
|
1189
|
+
List all agents with full detail (including system prompt).
|
|
1190
|
+
|
|
1191
|
+
#### `GET /admin/api/agents/:id`
|
|
1192
|
+
|
|
1193
|
+
Get a single agent by ID.
|
|
1194
|
+
|
|
1195
|
+
#### `POST /admin/api/agents`
|
|
1196
|
+
|
|
1197
|
+
Create an agent.
|
|
1198
|
+
|
|
1199
|
+
**Request Body:**
|
|
1200
|
+
|
|
1201
|
+
```json
|
|
1202
|
+
{
|
|
1203
|
+
"name": "Research Assistant",
|
|
1204
|
+
"description": "Searches and summarizes web content",
|
|
1205
|
+
"provider": "openai",
|
|
1206
|
+
"model": "gpt-4o",
|
|
1207
|
+
"systemPrompt": "You are a research assistant. Use tools to answer questions accurately.",
|
|
1208
|
+
"tools": ["web_search", "calculate"],
|
|
1209
|
+
"temperature": 0.7,
|
|
1210
|
+
"maxTokens": 2048,
|
|
1211
|
+
"maxIterations": 10
|
|
1212
|
+
}
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
**Response `201`:**
|
|
1216
|
+
|
|
1217
|
+
```json
|
|
1218
|
+
{
|
|
1219
|
+
"data": { "id": "uuid", "name": "Research Assistant", ... },
|
|
1220
|
+
"message": "Agent \"Research Assistant\" created."
|
|
1221
|
+
}
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
#### `PUT /admin/api/agents/:id`
|
|
1225
|
+
|
|
1226
|
+
Update an agent (partial patch). Same body shape as POST.
|
|
1227
|
+
|
|
1228
|
+
**Response `200`:**
|
|
1229
|
+
|
|
1230
|
+
```json
|
|
1231
|
+
{
|
|
1232
|
+
"data": { "id": "uuid", "name": "Research Assistant", ... },
|
|
1233
|
+
"message": "Agent \"Research Assistant\" updated."
|
|
1234
|
+
}
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
#### `DELETE /admin/api/agents/:id`
|
|
1238
|
+
|
|
1239
|
+
Delete an agent permanently.
|
|
1240
|
+
|
|
1241
|
+
**Response:** `204 No Content`
|
|
1242
|
+
|
|
1243
|
+
---
|
|
1244
|
+
|
|
1245
|
+
## 4. Authentication
|
|
1246
|
+
|
|
1247
|
+
### Flow
|
|
1248
|
+
|
|
1249
|
+
```
|
|
1250
|
+
Client Rotifex Server
|
|
1251
|
+
| |
|
|
1252
|
+
|-- POST /auth/register ----------> | Hash password, create user
|
|
1253
|
+
|<- { user } ---------------------- |
|
|
1254
|
+
| |
|
|
1255
|
+
|-- POST /auth/login -------------> | Verify password
|
|
1256
|
+
|<- { accessToken, refreshToken }-- |
|
|
1257
|
+
| |
|
|
1258
|
+
|-- GET /api/products |
|
|
1259
|
+
| Authorization: Bearer <token> > | Verify JWT, inject x-user-id/x-user-role
|
|
1260
|
+
|<- { data: [...] } --------------- |
|
|
1261
|
+
| |
|
|
1262
|
+
|-- POST /auth/refresh -----------> | Verify refresh token, issue new pair
|
|
1263
|
+
|<- { accessToken, refreshToken }-- |
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
### Token Details
|
|
1267
|
+
|
|
1268
|
+
| Token | Algorithm | TTL | Secret Env Var |
|
|
1269
|
+
| ------------- | --------- | ------- | -------------------- |
|
|
1270
|
+
| Access Token | HS256 | 1 hour | `JWT_SECRET` |
|
|
1271
|
+
| Refresh Token | HS256 | 30 days | `JWT_REFRESH_SECRET` |
|
|
1272
|
+
|
|
1273
|
+
Secrets are auto-generated and saved to `.env` on first startup if not explicitly set.
|
|
1274
|
+
|
|
1275
|
+
### Required Headers
|
|
1276
|
+
|
|
1277
|
+
| Header | Value | Set By |
|
|
1278
|
+
| --------------- | ---------------------- | ------------------------------ |
|
|
1279
|
+
| `Authorization` | `Bearer <accessToken>` | Client |
|
|
1280
|
+
| `x-user-id` | User UUID | JWT middleware (auto-injected) |
|
|
1281
|
+
| `x-user-role` | `user` or `admin` | JWT middleware (auto-injected) |
|
|
1282
|
+
|
|
1283
|
+
### Permission Levels
|
|
1284
|
+
|
|
1285
|
+
| Role | Access |
|
|
1286
|
+
| ------- | --------------------------------------------- |
|
|
1287
|
+
| `user` | Public endpoints, own files, own data records |
|
|
1288
|
+
| `admin` | All endpoints including `/admin/api/*` |
|
|
1289
|
+
|
|
1290
|
+
> The JWT middleware skips all `/auth/*` routes. For `/auth/me`, the token is manually verified inside the handler.
|
|
1291
|
+
|
|
1292
|
+
---
|
|
1293
|
+
|
|
1294
|
+
## 5. Models / Data Structures
|
|
1295
|
+
|
|
1296
|
+
### User
|
|
1297
|
+
|
|
1298
|
+
Built-in model managed by the auth system. Not configurable via the schema engine.
|
|
1299
|
+
|
|
1300
|
+
| Field | Type | Required | Notes |
|
|
1301
|
+
| --------------- | ----------------- | -------- | ----------------------------------- |
|
|
1302
|
+
| `id` | string (UUID) | Auto | Primary key |
|
|
1303
|
+
| `email` | string | Yes | Unique |
|
|
1304
|
+
| `display_name` | string | No | |
|
|
1305
|
+
| `role` | string | Yes | `"user"` or `"admin"` |
|
|
1306
|
+
| `password_hash` | string | Internal | bcrypt hash — never returned by API |
|
|
1307
|
+
| `created_at` | string (ISO 8601) | Auto | |
|
|
1308
|
+
| `updated_at` | string (ISO 8601) | Auto | |
|
|
1309
|
+
|
|
1310
|
+
---
|
|
1311
|
+
|
|
1312
|
+
### \_files (File Metadata)
|
|
1313
|
+
|
|
1314
|
+
Internal table managed by `StorageManager`. Not in `schema.json`.
|
|
1315
|
+
|
|
1316
|
+
| Field | Type | Notes |
|
|
1317
|
+
| --------------- | ----------------- | ---------------------------------------- |
|
|
1318
|
+
| `id` | string (UUID) | Primary key |
|
|
1319
|
+
| `original_name` | string | Original filename from upload |
|
|
1320
|
+
| `stored_name` | string | `<uuid>.<ext>` — actual filename on disk |
|
|
1321
|
+
| `mime_type` | string | e.g. `image/jpeg` |
|
|
1322
|
+
| `size_bytes` | integer | File size in bytes |
|
|
1323
|
+
| `visibility` | string | `"public"` or `"private"` |
|
|
1324
|
+
| `uploader_id` | string | UUID of the uploading user |
|
|
1325
|
+
| `created_at` | string (ISO 8601) | |
|
|
1326
|
+
|
|
1327
|
+
---
|
|
1328
|
+
|
|
1329
|
+
### Custom Models (schema.json)
|
|
1330
|
+
|
|
1331
|
+
All custom models automatically receive `id`, `created_at`, and `updated_at`.
|
|
1332
|
+
|
|
1333
|
+
**Example `schema.json`:**
|
|
1334
|
+
|
|
1335
|
+
```json
|
|
1336
|
+
{
|
|
1337
|
+
"Product": {
|
|
1338
|
+
"fields": {
|
|
1339
|
+
"name": { "type": "string", "required": true },
|
|
1340
|
+
"price": "number",
|
|
1341
|
+
"in_stock": "boolean",
|
|
1342
|
+
"sku": { "type": "string", "unique": true }
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
"Order": {
|
|
1346
|
+
"fields": {
|
|
1347
|
+
"product_id": { "type": "string", "required": true },
|
|
1348
|
+
"quantity": { "type": "integer", "required": true },
|
|
1349
|
+
"status": { "type": "string", "default": "pending" }
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
**Resulting routes:**
|
|
1356
|
+
|
|
1357
|
+
- `Product` -> table `products` -> `/api/products`, `/api/products/:id`
|
|
1358
|
+
- `Order` -> table `orders` -> `/api/orders`, `/api/orders/:id`
|
|
1359
|
+
|
|
1360
|
+
---
|
|
1361
|
+
|
|
1362
|
+
### Agent
|
|
1363
|
+
|
|
1364
|
+
Stored in `agents.config.json`.
|
|
1365
|
+
|
|
1366
|
+
| Field | Type | Default | Description |
|
|
1367
|
+
| --------------- | ----------------- | ------- | -------------------------------------------- |
|
|
1368
|
+
| `id` | string (UUID) | Auto | |
|
|
1369
|
+
| `name` | string | — | Display name |
|
|
1370
|
+
| `description` | string | `""` | Human-readable description |
|
|
1371
|
+
| `provider` | string | — | `openai`, `anthropic`, `gemini`, or `ollama` |
|
|
1372
|
+
| `model` | string | — | Model ID |
|
|
1373
|
+
| `systemPrompt` | string | `""` | Agent's system instruction |
|
|
1374
|
+
| `tools` | string[] | `[]` | List of tool names to enable |
|
|
1375
|
+
| `temperature` | number | `0.7` | Sampling temperature |
|
|
1376
|
+
| `maxTokens` | integer | `2048` | Max tokens per LLM call |
|
|
1377
|
+
| `maxIterations` | integer | `10` | Max tool-call loop iterations |
|
|
1378
|
+
| `createdAt` | string (ISO 8601) | Auto | |
|
|
1379
|
+
| `updatedAt` | string (ISO 8601) | Auto | |
|
|
1380
|
+
|
|
1381
|
+
---
|
|
1382
|
+
|
|
1383
|
+
### AI Provider (ai.config.json)
|
|
1384
|
+
|
|
1385
|
+
| Field | Type | Description |
|
|
1386
|
+
| -------------- | -------- | ---------------------------------- |
|
|
1387
|
+
| `label` | string | Display name |
|
|
1388
|
+
| `apiKey` | string | API key (empty string if not set) |
|
|
1389
|
+
| `enabled` | boolean | Whether the provider is active |
|
|
1390
|
+
| `models` | string[] | Available model IDs |
|
|
1391
|
+
| `defaultModel` | string | Default model ID |
|
|
1392
|
+
| `baseUrl` | string | Only for Ollama — local server URL |
|
|
1393
|
+
|
|
1394
|
+
---
|
|
1395
|
+
|
|
1396
|
+
### Token Usage (ai.usage.json)
|
|
1397
|
+
|
|
1398
|
+
```json
|
|
1399
|
+
{
|
|
1400
|
+
"totalRequests": 150,
|
|
1401
|
+
"totalInputTokens": 55000,
|
|
1402
|
+
"totalOutputTokens": 14000,
|
|
1403
|
+
"byProvider": {
|
|
1404
|
+
"openai": {
|
|
1405
|
+
"requests": 100,
|
|
1406
|
+
"inputTokens": 40000,
|
|
1407
|
+
"outputTokens": 10000
|
|
1408
|
+
},
|
|
1409
|
+
"anthropic": {
|
|
1410
|
+
"requests": 50,
|
|
1411
|
+
"inputTokens": 15000,
|
|
1412
|
+
"outputTokens": 4000
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
---
|
|
1419
|
+
|
|
1420
|
+
## 6. AI / LLM Integration
|
|
1421
|
+
|
|
1422
|
+
### Supported Providers
|
|
1423
|
+
|
|
1424
|
+
| Provider ID | Label | Requires API Key | Default Models |
|
|
1425
|
+
| ----------- | -------------- | ---------------- | ------------------------------------------------------------- |
|
|
1426
|
+
| `openai` | OpenAI | Yes | gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo |
|
|
1427
|
+
| `anthropic` | Anthropic | Yes | claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5-20251001 |
|
|
1428
|
+
| `gemini` | Google Gemini | Yes | gemini-2.0-flash, gemini-1.5-pro, gemini-1.5-flash |
|
|
1429
|
+
| `ollama` | Ollama (Local) | No | llama3.2, mistral, codellama, phi3 |
|
|
1430
|
+
|
|
1431
|
+
### Configuration
|
|
1432
|
+
|
|
1433
|
+
Providers are configured via:
|
|
1434
|
+
|
|
1435
|
+
1. **Admin panel -> AI Integration -> Providers tab:** Enable provider, paste API key, select default model.
|
|
1436
|
+
2. **Direct file edit:** Edit `ai.config.json` in the project root.
|
|
1437
|
+
|
|
1438
|
+
Ollama requires a running local Ollama server. Default base URL: `http://localhost:11434`.
|
|
1439
|
+
|
|
1440
|
+
### Token Usage Tracking
|
|
1441
|
+
|
|
1442
|
+
Every call to `POST /api/ai/generate`, `POST /api/ai/chat`, and `POST /api/agents/:id/run` records:
|
|
1443
|
+
|
|
1444
|
+
- Input token count
|
|
1445
|
+
- Output token count
|
|
1446
|
+
- Per-provider breakdown
|
|
1447
|
+
|
|
1448
|
+
Data is persisted to `ai.usage.json` and survives server restarts. Totals are visible in the admin dashboard.
|
|
1449
|
+
|
|
1450
|
+
### AI Playground (Admin Panel)
|
|
1451
|
+
|
|
1452
|
+
- **Generate mode:** Single prompt with provider, model, system prompt, temperature, max tokens controls. Shows token count after each request.
|
|
1453
|
+
- **Chat mode:** Multi-turn conversation with scrollable history and persistent context.
|
|
1454
|
+
- **Session totals:** Cumulative token counts for the current browser session with a reset button.
|
|
1455
|
+
|
|
1456
|
+
### Agent System
|
|
1457
|
+
|
|
1458
|
+
#### ReAct Loop
|
|
1459
|
+
|
|
1460
|
+
```
|
|
1461
|
+
User Input
|
|
1462
|
+
|
|
|
1463
|
+
v
|
|
1464
|
+
LLM (receives task + tool definitions)
|
|
1465
|
+
|
|
|
1466
|
+
+-- Tool call requested?
|
|
1467
|
+
| |
|
|
1468
|
+
| +-- Execute tool
|
|
1469
|
+
| +-- Append result to conversation
|
|
1470
|
+
| +-- Loop back to LLM
|
|
1471
|
+
|
|
|
1472
|
+
+-- Final answer -> return output + all steps to caller
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
#### Agent Step Types
|
|
1476
|
+
|
|
1477
|
+
| Type | Description |
|
|
1478
|
+
| -------------- | ------------------------------------------- |
|
|
1479
|
+
| `thinking` | LLM reasoning text before invoking a tool |
|
|
1480
|
+
| `tool_call` | Name and arguments of the tool being called |
|
|
1481
|
+
| `tool_result` | Raw output from the tool execution |
|
|
1482
|
+
| `final_answer` | The LLM's conclusive response |
|
|
1483
|
+
|
|
1484
|
+
---
|
|
1485
|
+
|
|
1486
|
+
## 7. File Storage / Media Handling
|
|
1487
|
+
|
|
1488
|
+
### Upload Process
|
|
1489
|
+
|
|
1490
|
+
1. Client sends `POST /files/upload` as `multipart/form-data` with a `file` field and optional `visibility` field.
|
|
1491
|
+
2. Server reads and buffers the stream, validates MIME type against the allowed list.
|
|
1492
|
+
3. Checks the file size against `maxFileSizeMB` (default 10 MB).
|
|
1493
|
+
4. Checks the uploader's total storage against `maxStoragePerUserMB` (default 100 MB).
|
|
1494
|
+
5. Writes the file to disk as `<uuid><original-extension>` in the appropriate directory.
|
|
1495
|
+
6. Inserts metadata into the `_files` SQLite table.
|
|
1496
|
+
7. Returns the full file metadata record.
|
|
1497
|
+
|
|
1498
|
+
### Access URLs
|
|
1499
|
+
|
|
1500
|
+
| Visibility | Download URL | Auth Required |
|
|
1501
|
+
| ---------- | ---------------------------------------------------- | ------------------------ |
|
|
1502
|
+
| `public` | `/files/:id/download` | No |
|
|
1503
|
+
| `private` | `/files/:id/download?token=<hmac>&expires=<unix-ts>` | Signed URL (HMAC-SHA256) |
|
|
1504
|
+
|
|
1505
|
+
Signed URLs are generated via `GET /files/:id/signed-url`. The default TTL is 1 hour, configurable via `signedUrlTTLSeconds` in config.
|
|
1506
|
+
|
|
1507
|
+
### Storage Structure
|
|
1508
|
+
|
|
1509
|
+
```
|
|
1510
|
+
<project-root>/
|
|
1511
|
+
storage/
|
|
1512
|
+
public/ <- Publicly downloadable files
|
|
1513
|
+
<uuid>.jpg
|
|
1514
|
+
<uuid>.png
|
|
1515
|
+
private/ <- Signed-URL-only files
|
|
1516
|
+
<uuid>.pdf
|
|
1517
|
+
<uuid>.docx
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
### Size and Quota Limits
|
|
1521
|
+
|
|
1522
|
+
| Setting | Environment Variable | Default |
|
|
1523
|
+
| -------------------- | ----------------------------------- | ------- |
|
|
1524
|
+
| Max file size | `ROTIFEX_STORAGE_MAX_FILE_SIZE_MB` | 10 MB |
|
|
1525
|
+
| Max storage per user | Config only (`maxStoragePerUserMB`) | 100 MB |
|
|
1526
|
+
|
|
1527
|
+
---
|
|
1528
|
+
|
|
1529
|
+
## 8. Admin Panel Features
|
|
1530
|
+
|
|
1531
|
+
### Dashboard
|
|
1532
|
+
|
|
1533
|
+
- **Stat cards:** Schemas, Total Records, Users, Files, Storage Used, Connected LLMs, Agents Created, Server Uptime, Server Status
|
|
1534
|
+
- **Schema Overview table:** model name, table name, record count per model
|
|
1535
|
+
- **Connected LLMs table:** provider name, request count, tokens in, tokens out, key status
|
|
1536
|
+
|
|
1537
|
+
### Database Schemas
|
|
1538
|
+
|
|
1539
|
+
- View all defined models with their field names, types, and constraints
|
|
1540
|
+
- Create a new model via a field builder UI (add field name + type pairs)
|
|
1541
|
+
- Attempting to create a model with an existing name returns a conflict error
|
|
1542
|
+
- Delete a model (routes deactivate immediately; underlying data table is preserved)
|
|
1543
|
+
|
|
1544
|
+
### User Management
|
|
1545
|
+
|
|
1546
|
+
- List all registered users: email, display name, role, creation date
|
|
1547
|
+
- Admin actions on user accounts
|
|
1548
|
+
|
|
1549
|
+
### File Browser
|
|
1550
|
+
|
|
1551
|
+
- Browse all files (admins see all; users see their own)
|
|
1552
|
+
- Preview images inline
|
|
1553
|
+
- Download any file
|
|
1554
|
+
- Delete files (removes from disk and database)
|
|
1555
|
+
|
|
1556
|
+
### AI Integration
|
|
1557
|
+
|
|
1558
|
+
**Providers tab:** Enable/disable providers, enter API keys (masked after save), set default model per provider.
|
|
1559
|
+
|
|
1560
|
+
**Playground tab:**
|
|
1561
|
+
|
|
1562
|
+
- Generate mode: prompt + provider/model/system/temp/maxTokens controls, token display after response
|
|
1563
|
+
- Chat mode: multi-turn conversation with message history
|
|
1564
|
+
|
|
1565
|
+
**Agents tab:**
|
|
1566
|
+
|
|
1567
|
+
- List all agents with name, provider, model, tools
|
|
1568
|
+
- Create agent form: name, description, provider, model, system prompt, tool checkboxes, temperature, max tokens, max iterations
|
|
1569
|
+
- Edit existing agents
|
|
1570
|
+
- Delete agents
|
|
1571
|
+
- Run agents interactively: enter a task, see reasoning steps in real time, view final output
|
|
1572
|
+
|
|
1573
|
+
**API Docs tab:** Built-in reference for all AI and agent endpoints.
|
|
1574
|
+
|
|
1575
|
+
### Server Logs
|
|
1576
|
+
|
|
1577
|
+
- In-memory ring buffer of structured log entries
|
|
1578
|
+
- Filter by level (`info`, `warn`, `error`, `debug`)
|
|
1579
|
+
- Timestamps and log messages displayed in a table
|
|
1580
|
+
|
|
1581
|
+
### Settings
|
|
1582
|
+
|
|
1583
|
+
Editable via admin panel — writes to `.env`:
|
|
1584
|
+
|
|
1585
|
+
| Variable | Description |
|
|
1586
|
+
| ----------------------------------- | ---------------------------- |
|
|
1587
|
+
| `JWT_SECRET` | Access token signing secret |
|
|
1588
|
+
| `JWT_REFRESH_SECRET` | Refresh token signing secret |
|
|
1589
|
+
| `ROTIFEX_PORT` | Server port |
|
|
1590
|
+
| `ROTIFEX_HOST` | Server bind host |
|
|
1591
|
+
| `ROTIFEX_CORS_ORIGIN` | Allowed CORS origin(s) |
|
|
1592
|
+
| `ROTIFEX_RATE_LIMIT_MAX` | Max requests per time window |
|
|
1593
|
+
| `ROTIFEX_LOG_LEVEL` | Log verbosity |
|
|
1594
|
+
| `ROTIFEX_STORAGE_MAX_FILE_SIZE_MB` | Max upload size in MB |
|
|
1595
|
+
| `ROTIFEX_STORAGE_SIGNED_URL_SECRET` | HMAC secret for signed URLs |
|
|
1596
|
+
|
|
1597
|
+
---
|
|
1598
|
+
|
|
1599
|
+
## 9. Error Handling
|
|
1600
|
+
|
|
1601
|
+
### Error Response Format
|
|
1602
|
+
|
|
1603
|
+
```json
|
|
1604
|
+
{
|
|
1605
|
+
"error": "Error Type",
|
|
1606
|
+
"message": "Human-readable description of what went wrong.",
|
|
1607
|
+
"statusCode": 400
|
|
1608
|
+
}
|
|
1609
|
+
```
|
|
1610
|
+
|
|
1611
|
+
Validation errors may return an array for `message`:
|
|
1612
|
+
|
|
1613
|
+
```json
|
|
1614
|
+
{
|
|
1615
|
+
"error": "Validation Error",
|
|
1616
|
+
"message": [{ "path": ["name"], "message": "Required" }],
|
|
1617
|
+
"statusCode": 400
|
|
1618
|
+
}
|
|
1619
|
+
```
|
|
1620
|
+
|
|
1621
|
+
### Common HTTP Error Codes
|
|
1622
|
+
|
|
1623
|
+
| Code | Meaning | Common Causes |
|
|
1624
|
+
| ----- | --------------------- | ------------------------------------------------------------------------------------- |
|
|
1625
|
+
| `400` | Bad Request | Missing required fields, invalid types, reserved model names, no fields to update |
|
|
1626
|
+
| `401` | Unauthorized | Missing, expired, or invalid JWT access token |
|
|
1627
|
+
| `403` | Forbidden | Non-admin accessing `/admin/api/*`, file access without ownership or valid signed URL |
|
|
1628
|
+
| `404` | Not Found | Unknown table name, record ID not found, agent not found, file not found |
|
|
1629
|
+
| `409` | Conflict | Email already registered, model name already exists |
|
|
1630
|
+
| `413` | Payload Too Large | File exceeds per-request size limit or per-user storage quota |
|
|
1631
|
+
| `500` | Internal Server Error | Unexpected server error, LLM provider failure |
|
|
1632
|
+
|
|
1633
|
+
---
|
|
1634
|
+
|
|
1635
|
+
## 10. Environment Configuration
|
|
1636
|
+
|
|
1637
|
+
### Configuration Priority (highest to lowest)
|
|
1638
|
+
|
|
1639
|
+
1. Shell environment variables
|
|
1640
|
+
2. CLI flags (`--port`, `--host`, `--verbose`)
|
|
1641
|
+
3. `config.json` (optional user overrides)
|
|
1642
|
+
4. `config.default.json` (shipped defaults)
|
|
1643
|
+
5. `.env` file (auto-loaded at startup)
|
|
1644
|
+
|
|
1645
|
+
### Environment Variables Reference
|
|
1646
|
+
|
|
1647
|
+
| Variable | Default | Description |
|
|
1648
|
+
| ----------------------------------- | --------- | -------------------------------------------- |
|
|
1649
|
+
| `ROTIFEX_PORT` | `3000` | TCP port |
|
|
1650
|
+
| `ROTIFEX_HOST` | `0.0.0.0` | Bind address |
|
|
1651
|
+
| `ROTIFEX_CORS_ORIGIN` | `*` | Allowed CORS origin |
|
|
1652
|
+
| `ROTIFEX_RATE_LIMIT_MAX` | `100` | Max requests per rate-limit window |
|
|
1653
|
+
| `ROTIFEX_LOG_LEVEL` | `info` | Log level (`info`, `debug`, `warn`, `error`) |
|
|
1654
|
+
| `ROTIFEX_STORAGE_MAX_FILE_SIZE_MB` | `10` | Max upload size in MB |
|
|
1655
|
+
| `ROTIFEX_STORAGE_SIGNED_URL_SECRET` | auto | HMAC secret for signed file URLs |
|
|
1656
|
+
| `JWT_SECRET` | auto | Access token signing secret |
|
|
1657
|
+
| `JWT_REFRESH_SECRET` | auto | Refresh token signing secret |
|
|
1658
|
+
|
|
1659
|
+
> `JWT_SECRET`, `JWT_REFRESH_SECRET`, and `ROTIFEX_STORAGE_SIGNED_URL_SECRET` are auto-generated on first startup if absent and saved to `.env`.
|
|
1660
|
+
|
|
1661
|
+
### Example `.env`
|
|
1662
|
+
|
|
1663
|
+
```env
|
|
1664
|
+
ROTIFEX_PORT=3000
|
|
1665
|
+
ROTIFEX_HOST=0.0.0.0
|
|
1666
|
+
ROTIFEX_CORS_ORIGIN=https://myapp.com
|
|
1667
|
+
ROTIFEX_RATE_LIMIT_MAX=200
|
|
1668
|
+
ROTIFEX_LOG_LEVEL=info
|
|
1669
|
+
ROTIFEX_STORAGE_MAX_FILE_SIZE_MB=25
|
|
1670
|
+
JWT_SECRET=replace-with-a-long-random-string
|
|
1671
|
+
JWT_REFRESH_SECRET=replace-with-another-long-random-string
|
|
1672
|
+
ROTIFEX_STORAGE_SIGNED_URL_SECRET=replace-with-yet-another-secret
|
|
1673
|
+
```
|
|
1674
|
+
|
|
1675
|
+
---
|
|
1676
|
+
|
|
1677
|
+
## 11. Deployment
|
|
1678
|
+
|
|
1679
|
+
### Requirements
|
|
1680
|
+
|
|
1681
|
+
- Node.js 18 or later
|
|
1682
|
+
- npm 9 or later
|
|
1683
|
+
|
|
1684
|
+
### Setup
|
|
1685
|
+
|
|
1686
|
+
```bash
|
|
1687
|
+
git clone <repo-url>
|
|
1688
|
+
cd rotifex
|
|
1689
|
+
npm install
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
### Running in Development
|
|
1693
|
+
|
|
1694
|
+
```bash
|
|
1695
|
+
# Default port 3000
|
|
1696
|
+
npx rotifex start
|
|
1697
|
+
|
|
1698
|
+
# Custom port
|
|
1699
|
+
npx rotifex start --port 4000
|
|
1700
|
+
|
|
1701
|
+
# Custom host
|
|
1702
|
+
npx rotifex start --host 127.0.0.1
|
|
1703
|
+
|
|
1704
|
+
# Verbose (debug) logging
|
|
1705
|
+
npx rotifex start --verbose
|
|
1706
|
+
```
|
|
1707
|
+
|
|
1708
|
+
### Build the Admin Dashboard
|
|
1709
|
+
|
|
1710
|
+
```bash
|
|
1711
|
+
npm run build:admin
|
|
1712
|
+
```
|
|
1713
|
+
|
|
1714
|
+
Output is written to `admin/dist/`. The server automatically serves it at `/` when the directory exists.
|
|
1715
|
+
|
|
1716
|
+
### Production Setup
|
|
1717
|
+
|
|
1718
|
+
**1. Set secrets in environment:**
|
|
1719
|
+
|
|
1720
|
+
```bash
|
|
1721
|
+
export JWT_SECRET="$(openssl rand -hex 32)"
|
|
1722
|
+
export JWT_REFRESH_SECRET="$(openssl rand -hex 32)"
|
|
1723
|
+
export ROTIFEX_STORAGE_SIGNED_URL_SECRET="$(openssl rand -hex 32)"
|
|
1724
|
+
export ROTIFEX_CORS_ORIGIN="https://yourfrontend.com"
|
|
1725
|
+
```
|
|
1726
|
+
|
|
1727
|
+
**2. Build the admin dashboard:**
|
|
1728
|
+
|
|
1729
|
+
```bash
|
|
1730
|
+
npm run build:admin
|
|
1731
|
+
```
|
|
1732
|
+
|
|
1733
|
+
**3. Use a process manager:**
|
|
1734
|
+
|
|
1735
|
+
```bash
|
|
1736
|
+
npm install -g pm2
|
|
1737
|
+
pm2 start "npx rotifex start" --name rotifex
|
|
1738
|
+
pm2 save
|
|
1739
|
+
pm2 startup
|
|
1740
|
+
```
|
|
1741
|
+
|
|
1742
|
+
**4. Reverse proxy (Nginx example):**
|
|
1743
|
+
|
|
1744
|
+
```nginx
|
|
1745
|
+
server {
|
|
1746
|
+
listen 80;
|
|
1747
|
+
server_name api.yourapp.com;
|
|
1748
|
+
|
|
1749
|
+
location / {
|
|
1750
|
+
proxy_pass http://127.0.0.1:3000;
|
|
1751
|
+
proxy_set_header Host $host;
|
|
1752
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
1753
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
```
|
|
1757
|
+
|
|
1758
|
+
**5. Backup the database:**
|
|
1759
|
+
|
|
1760
|
+
```bash
|
|
1761
|
+
# SQLite database file — copy it regularly
|
|
1762
|
+
cp rotifex.db rotifex.db.backup
|
|
1763
|
+
```
|
|
1764
|
+
|
|
1765
|
+
---
|
|
1766
|
+
|
|
1767
|
+
## 12. Example Workflows
|
|
1768
|
+
|
|
1769
|
+
### Registering a User and Logging In
|
|
1770
|
+
|
|
1771
|
+
```bash
|
|
1772
|
+
# Register
|
|
1773
|
+
curl -X POST http://localhost:3000/auth/register \
|
|
1774
|
+
-H "Content-Type: application/json" \
|
|
1775
|
+
-d '{"email":"jane@example.com","password":"secure123","display_name":"Jane"}'
|
|
1776
|
+
|
|
1777
|
+
# Login — save the returned accessToken
|
|
1778
|
+
curl -X POST http://localhost:3000/auth/login \
|
|
1779
|
+
-H "Content-Type: application/json" \
|
|
1780
|
+
-d '{"email":"jane@example.com","password":"secure123"}'
|
|
1781
|
+
|
|
1782
|
+
# Store token
|
|
1783
|
+
export TOKEN="<accessToken from response>"
|
|
1784
|
+
```
|
|
1785
|
+
|
|
1786
|
+
---
|
|
1787
|
+
|
|
1788
|
+
### Defining a Model and Using the CRUD API
|
|
1789
|
+
|
|
1790
|
+
```bash
|
|
1791
|
+
# 1. Create model (admin role required)
|
|
1792
|
+
curl -X POST http://localhost:3000/admin/api/schema \
|
|
1793
|
+
-H "Content-Type: application/json" \
|
|
1794
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
1795
|
+
-d '{
|
|
1796
|
+
"name": "Product",
|
|
1797
|
+
"fields": {
|
|
1798
|
+
"name": {"type": "string", "required": true},
|
|
1799
|
+
"price": "number",
|
|
1800
|
+
"in_stock": "boolean"
|
|
1801
|
+
}
|
|
1802
|
+
}'
|
|
1803
|
+
|
|
1804
|
+
# 2. Create a record
|
|
1805
|
+
curl -X POST http://localhost:3000/api/products \
|
|
1806
|
+
-H "Content-Type: application/json" \
|
|
1807
|
+
-d '{"name":"Widget","price":9.99,"in_stock":true}'
|
|
1808
|
+
|
|
1809
|
+
# 3. List with sort and filter
|
|
1810
|
+
curl "http://localhost:3000/api/products?sort=price&order=ASC&in_stock=1"
|
|
1811
|
+
|
|
1812
|
+
# 4. Get one
|
|
1813
|
+
curl http://localhost:3000/api/products/<id>
|
|
1814
|
+
|
|
1815
|
+
# 5. Update
|
|
1816
|
+
curl -X PUT http://localhost:3000/api/products/<id> \
|
|
1817
|
+
-H "Content-Type: application/json" \
|
|
1818
|
+
-d '{"price":14.99}'
|
|
1819
|
+
|
|
1820
|
+
# 6. Delete
|
|
1821
|
+
curl -X DELETE http://localhost:3000/api/products/<id>
|
|
1822
|
+
```
|
|
1823
|
+
|
|
1824
|
+
---
|
|
1825
|
+
|
|
1826
|
+
### Uploading and Downloading Files
|
|
1827
|
+
|
|
1828
|
+
```bash
|
|
1829
|
+
# Upload a public file
|
|
1830
|
+
curl -X POST http://localhost:3000/files/upload \
|
|
1831
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
1832
|
+
-F "file=@/path/to/photo.jpg" \
|
|
1833
|
+
-F "visibility=public"
|
|
1834
|
+
|
|
1835
|
+
# Download public file (no auth needed)
|
|
1836
|
+
curl http://localhost:3000/files/<id>/download -o photo.jpg
|
|
1837
|
+
|
|
1838
|
+
# Upload a private file
|
|
1839
|
+
curl -X POST http://localhost:3000/files/upload \
|
|
1840
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
1841
|
+
-F "file=@/path/to/document.pdf" \
|
|
1842
|
+
-F "visibility=private"
|
|
1843
|
+
|
|
1844
|
+
# Get a signed URL for the private file
|
|
1845
|
+
curl http://localhost:3000/files/<id>/signed-url \
|
|
1846
|
+
-H "Authorization: Bearer $TOKEN"
|
|
1847
|
+
|
|
1848
|
+
# Download using the signed URL
|
|
1849
|
+
curl "<signed-url>" -o document.pdf
|
|
1850
|
+
```
|
|
1851
|
+
|
|
1852
|
+
---
|
|
1853
|
+
|
|
1854
|
+
### Calling an AI Model
|
|
1855
|
+
|
|
1856
|
+
```bash
|
|
1857
|
+
# Generate a completion
|
|
1858
|
+
curl -X POST http://localhost:3000/api/ai/generate \
|
|
1859
|
+
-H "Content-Type: application/json" \
|
|
1860
|
+
-d '{
|
|
1861
|
+
"provider": "openai",
|
|
1862
|
+
"model": "gpt-4o",
|
|
1863
|
+
"prompt": "Write a one-sentence tagline for a productivity app.",
|
|
1864
|
+
"maxTokens": 60
|
|
1865
|
+
}'
|
|
1866
|
+
|
|
1867
|
+
# Multi-turn chat
|
|
1868
|
+
curl -X POST http://localhost:3000/api/ai/chat \
|
|
1869
|
+
-H "Content-Type: application/json" \
|
|
1870
|
+
-d '{
|
|
1871
|
+
"provider": "anthropic",
|
|
1872
|
+
"model": "claude-sonnet-4-6",
|
|
1873
|
+
"messages": [
|
|
1874
|
+
{"role": "user", "content": "What is the capital of France?"}
|
|
1875
|
+
]
|
|
1876
|
+
}'
|
|
1877
|
+
```
|
|
1878
|
+
|
|
1879
|
+
---
|
|
1880
|
+
|
|
1881
|
+
### Creating and Running an AI Agent
|
|
1882
|
+
|
|
1883
|
+
```bash
|
|
1884
|
+
# 1. Create an agent (admin required)
|
|
1885
|
+
curl -X POST http://localhost:3000/admin/api/agents \
|
|
1886
|
+
-H "Content-Type: application/json" \
|
|
1887
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
1888
|
+
-d '{
|
|
1889
|
+
"name": "Math Helper",
|
|
1890
|
+
"provider": "openai",
|
|
1891
|
+
"model": "gpt-4o",
|
|
1892
|
+
"systemPrompt": "You are a precise math assistant. Use the calculator tool for all computations.",
|
|
1893
|
+
"tools": ["calculate"],
|
|
1894
|
+
"temperature": 0.2,
|
|
1895
|
+
"maxIterations": 5
|
|
1896
|
+
}'
|
|
1897
|
+
|
|
1898
|
+
# 2. Run the agent
|
|
1899
|
+
curl -X POST http://localhost:3000/api/agents/<id>/run \
|
|
1900
|
+
-H "Content-Type: application/json" \
|
|
1901
|
+
-d '{"input": "What is (144 / 12) * 7.5 plus 33?"}'
|
|
1902
|
+
```
|
|
1903
|
+
|
|
1904
|
+
---
|
|
1905
|
+
|
|
1906
|
+
### Refreshing an Expired Token
|
|
1907
|
+
|
|
1908
|
+
```bash
|
|
1909
|
+
curl -X POST http://localhost:3000/auth/refresh \
|
|
1910
|
+
-H "Content-Type: application/json" \
|
|
1911
|
+
-d '{"refreshToken":"<your-refresh-token>"}'
|
|
1912
|
+
```
|
|
1913
|
+
|
|
1914
|
+
---
|
|
1915
|
+
|
|
1916
|
+
## 13. Notes for Documentation Generators
|
|
1917
|
+
|
|
1918
|
+
### Docusaurus
|
|
1919
|
+
|
|
1920
|
+
- Place this file in `docs/` as `intro.md`.
|
|
1921
|
+
- Split by `##` heading into separate files under `docs/api/`, `docs/guides/`, `docs/features/` for large doc sites.
|
|
1922
|
+
- Add `sidebar_label` and `sidebar_position` frontmatter to each split file.
|
|
1923
|
+
- All code blocks use fenced syntax compatible with Docusaurus's Prism highlighter.
|
|
1924
|
+
- Tables use standard GFM pipe syntax — no conversion needed.
|
|
1925
|
+
|
|
1926
|
+
### Mintlify
|
|
1927
|
+
|
|
1928
|
+
- Each `##` section maps to a separate `.mdx` page.
|
|
1929
|
+
- API endpoint documentation can be supplemented by an `openapi.yaml` file generated from §3.
|
|
1930
|
+
- Use `<Note>`, `<Warning>`, and `<Tip>` components for callouts when converting to Mintlify MDX.
|
|
1931
|
+
|
|
1932
|
+
### GitBook
|
|
1933
|
+
|
|
1934
|
+
- Import this file directly. GitBook renders all standard Markdown including tables, code blocks, and nested lists.
|
|
1935
|
+
- Use GitBook's `{% hint style="info" %}` blocks for the notes sections if converting to native GitBook format.
|
|
1936
|
+
|
|
1937
|
+
### Swagger / OpenAPI Mapping
|
|
1938
|
+
|
|
1939
|
+
This document contains enough information to produce a complete `openapi.yaml`. Use this mapping:
|
|
1940
|
+
|
|
1941
|
+
| Section | OpenAPI Component |
|
|
1942
|
+
| -------------------- | ------------------------------------------------ |
|
|
1943
|
+
| §3.2 Auth endpoints | `paths` under `/auth/*` |
|
|
1944
|
+
| §3.3 CRUD endpoints | `paths` under `/api/{table}` |
|
|
1945
|
+
| §3.4 File endpoints | `paths` under `/files/*` |
|
|
1946
|
+
| §3.5 AI endpoints | `paths` under `/api/ai/*` |
|
|
1947
|
+
| §3.6 Agent endpoints | `paths` under `/api/agents/*` |
|
|
1948
|
+
| §3.7 Admin endpoints | `paths` under `/admin/api/*` |
|
|
1949
|
+
| §5 Data Structures | `components/schemas` |
|
|
1950
|
+
| §9 Error codes | `components/responses` |
|
|
1951
|
+
| §4 Auth headers | `components/securitySchemes` (BearerAuth, HS256) |
|
|
1952
|
+
|
|
1953
|
+
### General Formatting Notes
|
|
1954
|
+
|
|
1955
|
+
- All code blocks specify a language (`bash`, `json`, `nginx`) for syntax highlighting.
|
|
1956
|
+
- Headings use a strict hierarchy (`#` -> `##` -> `###` -> `####`) for correct TOC generation.
|
|
1957
|
+
- Internal anchor links use lowercase slugs compatible with GitHub, Docusaurus, Mintlify, and GitBook.
|
|
1958
|
+
- No HTML tags are used — the file is pure Markdown for maximum portability.
|