API Reference
Base: backend PORT (default 8090; compose maps 8090:3000). All tenant routes nest under /api/v1/tenants/:tenantID/... (tenant.v1.routes.js, gated by authProvider + audit middleware). Responses spread data top-level: {success, ...data} (expressUtils.sendResponse).
Auth headers
| Identity | Header | Notes |
|---|---|---|
| User | Authorization: Bearer <Firebase ID token> | Verified per request; Socket.IO via handshake.auth.token |
| API key | Authorization: api_key <raw-key> | Prefix + SHA-256 hash check; isDisabled kills instantly |
| Operator | Authorization: Bearer <opaque-session> | /api/v1/operator* only; 12 h expiry |
Every tenant request also requires membership (checkTenantMembership) + Casbin authorize(P.resource.action) on the domain :tenantID.
Error mapping (index.js + error.util.js)
error.code | HTTP | When |
|---|---|---|
PERMISSION_DENIED | 403 | Casbin deny |
INVALID_API_KEY, USER_AUTH_TOKEN_EXPIRED, USER_AUTH_TOKEN_NOT_FOUND, INVALID_LOGIN | 401 | Bad/expired credential |
VALIDATION_ERROR, INVALID_REQUEST | 400 | Zod reject / unknown route shape |
| anything else | err.statusCode or 500 | {success:false, error} |
Unknown paths: ALL * → 404 {error: INVALID_REQUEST}.
Routes
Top-level (apps/backend/index.js):
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /health | none | {status:'ok', timestamp} |
| * | /api/v1/auth | mixed | GET /, GET /config/:tenantID, POST /config/:tenantID |
| * | /api/v1/tenants | user/API key | tenant CRUD + everything below |
| POST | /api/v1/operator/auth/login, /logout; GET /me | operator | opaque sessions |
| * | /api/v1/operator | operator session | roles, permissions, widget-library admin |
| POST | /api/v1/tenants/:tenantID/ai/chat/stream | tenant + ai.execute | SSE/streaming agent; DELETE /session resets |
| GET | /api/v1/oauth/google/auth/:tenantID, /:tenantID/google/url, /google/callback | callback public | 10-min JWT state |
| ALL | /webhooks/v1/inbound/:tenantID/:pathSuffix, /v1/inbound/:listenerID | none (suffix is secret) | open CORS |
Tenant-nested (/api/v1/tenants/:tenantID/..., permission in brackets):
| Prefix | Key endpoints |
|---|---|
/users [user.*] | GET /, POST /, `GET |
/roles [role.*] | `GET |
/apikeys [apikey.*] | `GET |
/datasources [datasource.*] | GET /schemas, `GET |
/queries [dataquery.*] | GET /schemas, `GET |
/workflows [workflow.*] | GET /schemas, `GET |
/widgets [widget.*] | GET /schemas, `GET |
/app-pages [appPage.*] | GET /schemas, `GET |
/listeners [listener.*] | GET /status/connections, GET /schemas, `GET |
/cronjobs [cronJob.*] | `GET |
/folders [folder.*] | GET /?entityType=, POST /, POST /move, `PATCH |
/import [bundle.*] | POST /preview, POST /execute |
/widget-library [widgetLibrary.*] | `GET |
/audit [audit.list] | GET /, GET /export (CSV) |
/:tenantID/ai-config | `GET |
POST /upload-logo | multer 10 MB |
Entity lists accept ?folderID=<uuid>; creating any asset grants the creator Casbin * on it.
Socket.IO (config/socket.io.js, index.js)
Client emits: workflow_run_join, widget_workflow_connect, widget_send_input, widget_refresh, widget_workflow_disconnect, join_room|leave_room (listener_test:|tenant:|listener:). Server emits: workflow_data_collection_request, workflow_node_update, workflow_status_update, widget_workflow_connected, widget_context_update, widget_workflow_status. Origins restricted to CORS_WHITELIST.
MCP (apps/mcp-server, :5001)
POST|GET /tenants/:tenantID/mcp (Firebase verifyIdToken per request, StreamableHTTPServerTransport), GET /health → {status, tools, timestamp}. Requires JET_ADMIN_BACKEND_URL + FIREBASE_CREDENTIALS. Tool list served from @jet-admin/mcp-server (allTools).
Rate limits
None enforced in code (no express-rate-limit; legacy nginx.conf limit_req zones are not in the active compose nginx). Apply limits at the edge until in-app limiting lands.