Plugin Endpoints
REST endpoints that become available when the corresponding plugin is installed — auth, workflow, automation, views, realtime, notifications, AI, i18n, GraphQL, and file storage.
Plugin Endpoints
These REST endpoints are only available when the corresponding plugin is installed. Check the discovery manifest services map before calling them; all paths are relative to the base URL (defaults to /api/v1).
Authentication (Plugin Required)
These endpoints are only available when an auth plugin is installed. Check discovery.services.auth.enabled first.
POST /auth/sign-in/email
Authenticate with email and password (better-auth's email sign-in route, mounted under the auth plugin's /auth prefix) and receive a session token. There is no /auth/login route.
Body: { email: "admin@example.com", password: "secret" }
Response: { token: "jwt...", user: { ... } }
Plugin-Provided Service Endpoints
The following endpoints become available when the corresponding plugin is installed and registered with the kernel. Use the discovery services map to check availability.
Workflow (/workflow) — removed in v17
There is no workflow endpoint, and there is no workflow service slot. The
three routes documented here were declared in the API protocol and served by
nothing — no dispatcher handler, no plugin — so they 404'd for the whole life
of the declaration. The slot, the WorkflowProtocol methods behind it and the
discovery fields that reported it were all retired in v17 (#4451).
Use the live mechanisms instead: an object validation rule of type
state_machine for lifecycle transitions, an approval flow node for human
approval pauses (ADR-0019), and lifecycle hooks / record_change flows for
record-triggered automation.
Approve/reject were never workflow routes (ADR-0019): approval is a flow node, and decisions are recorded on the approvals runtime via POST /approvals/requests/:id/approve and POST /approvals/requests/:id/reject.
Automation (/automation) — Plugin Required
| Method | Endpoint | Description |
|---|---|---|
| POST | /automation/:name/trigger | Trigger an automation flow by name (legacy alias: /automation/trigger/:name) |
The automation dispatcher also exposes flow CRUD (GET/POST /automation, GET/PUT/DELETE /automation/:name) and run observability/resume routes — see Durable pause & resume.
Views (/ui) — Plugin Required
Not yet mounted. The /ui/views CRUD routes are declared in the API protocol but no handler is registered for them, so they return 404 today (and the /api/v1/ui prefix is itself deprecated in favour of /api/v1/meta/view). The always-available view resolver is a separate route — see the note below.
| Method | Endpoint | Description |
|---|---|---|
| GET | /ui/views/:object | List views for an object |
| GET | /ui/views/:object/:viewId | Get a view definition |
| POST | /ui/views/:object | Create a new view |
| PATCH | /ui/views/:object/:viewId | Update a view |
| DELETE | /ui/views/:object/:viewId | Delete a view |
The auto-generated (non-CRUD) view resolver GET /ui/view/:object/:type is always available and provided by the kernel — see Metadata API. It is a separate route from the /ui/views CRUD above.
Realtime (/realtime) — Plugin Required
Not implemented. There is no realtime HTTP or WebSocket surface: @objectstack/service-realtime is an in-process pub/sub bus consumed via kernel.getService('realtime'), not over HTTP. The dispatcher has no /realtime branch, discovery deliberately omits the route (features.websockets is false), and the routes below would return 404 (ADR-0076 D12). They document the planned protocol shape only.
| Method | Endpoint | Description |
|---|---|---|
| POST | /realtime/connect | Establish WebSocket/SSE connection |
| POST | /realtime/disconnect | Close connection |
| POST | /realtime/subscribe | Subscribe to channel |
| POST | /realtime/unsubscribe | Unsubscribe |
| PUT | /realtime/presence/:channel | Set presence |
| GET | /realtime/presence/:channel | Get channel presence |
Notifications (/notifications) — Plugin Required
| Method | Endpoint | Description |
|---|---|---|
| GET | /notifications | List notifications (inbox) |
| POST | /notifications/read | Mark notifications as read (body: { ids: string[] }) |
| POST | /notifications/read/all | Mark all as read |
The core dispatcher implements only the list / read / read-all routes above. Device-registration and preference endpoints are provided by specific notification plugins, if any.
AI (/ai) — Plugin Required
These are the routes service-ai mounts, and the SDK method that reaches each:
| Method | Endpoint | SDK | Description |
|---|---|---|---|
| POST | /ai/chat | ai.chat / ai.chatStream | Chat completion — JSON with stream: false, otherwise the Vercel UI Message Stream |
| POST | /ai/chat/stream | — | Generic-SSE twin of /ai/chat: the same completion without the tool loop or persistence |
| POST | /ai/complete | ai.complete | Text completion |
| GET | /ai/models | ai.models | Models this environment offers (ADR-0028) |
| GET | /ai/status | — | Active adapter provenance (console diagnostics) |
| GET | /ai/effective-model | — | Resolved model ids and their source (console diagnostics) |
| POST / GET | /ai/conversations | ai.conversations.create / .list | Create / list conversations |
| GET / PATCH / DELETE | /ai/conversations/:id | ai.conversations.get / .update / .delete | Read / update / delete |
| POST | /ai/conversations/:id/messages | ai.conversations.addMessage | Append a message |
This table used to be inverted (#3718). It listed /ai/nlq,
/ai/suggest and /ai/insights — none of which any plugin has ever mounted —
and its callout stated "there is no /ai/chat route", which was wrong.
The three phantom routes were declared in DEFAULT_AI_ROUTES and called by
client.ai.nlq/suggest/insights; every call 404ed. v17 removed that SDK
namespace rather than build endpoints for it, and the same issue then gave
the SDK the surface that does exist — the ai.* column above.
Reviewed dispositions for all 12 routes live in the cloud repo,
packages/service-ai/src/ai-route-ledger.ts, whose conformance test reads
buildAIRoutes() and drives the SDK against it. The three rows with no SDK
method are deliberate: /status and /effective-model are operator
diagnostics, and /chat/stream is superseded by /chat's streaming mode.
For a React chat UI prefer useChat() (@ai-sdk/react) — it speaks the same
protocol ai.chatStream parses and owns message state for you.
i18n (/i18n) — Plugin Required
| Method | Endpoint | Description |
|---|---|---|
| GET | /i18n/locales | List available locales |
| GET | /i18n/translations/:locale | Get translation bundle |
| GET | /i18n/labels/:object/:locale | Get field labels |
GraphQL (/graphql) — Plugin Required
| Method | Endpoint | Description |
|---|---|---|
| POST | /graphql | Execute GraphQL query/mutation |
File Storage (/storage) — Plugin Required
Provided by @objectstack/service-storage, which registers these routes on the
host HTTP server. Uploads are a three-step protocol — ask for a presigned
target, send the bytes straight to it, then commit — so the bytes never proxy
through the API server, and the same shape works for S3, local disk, or any
other adapter.
| Method | Endpoint | SDK | Description |
|---|---|---|---|
| POST | /storage/upload/presigned | storage.getPresignedUrl | Step 1 — mint an upload target for a new sys_file row |
| POST | /storage/upload/complete | storage.upload | Step 3 — commit the uploaded bytes |
| POST | /storage/upload/chunked | storage.initChunkedUpload | Open a chunked / resumable session |
| PUT | /storage/upload/chunked/:uploadId/chunk/:chunkIndex | storage.uploadPart | Send one chunk |
| POST | /storage/upload/chunked/:uploadId/complete | storage.completeChunkedUpload | Assemble the parts |
| GET | /storage/upload/chunked/:uploadId/progress | storage.resumeUpload | Read session progress (first step of a resume) |
| GET | /storage/files/:fileId/url | storage.getDownloadUrl | Resolve a short-lived signed download URL |
| GET | /storage/files/:fileId | — | Stable browser URL; 302s to the same signed URL (what file fields carry) |
storage.upload(file) runs steps 1–3 for you, including the direct-to-storage
PUT in step 2.
This table used to list two routes that never worked (#4087). POST /storage/upload and GET /storage/file/:id were a dispatcher-side bridge to
the file-storage service, written against a service shape that does not
exist: it called upload(key, data, options?) as upload(file, { request }) —
a TypeError against every implementation in the repo — and read the Buffer
that download(key) resolves as if it were a { url | stream | mimeType }
descriptor. They stayed mounted and reachable — nothing shadowed them — so
anyone who followed this table got a 500; everyone who followed the SDK used
the protocol above and never touched them.
Both routes are retired. /storage is service-storage's surface — install that
package to get it. Discovery advertises the route only when the occupant of the
file-storage slot actually mounts HTTP handlers, so an in-memory dev
implementation now reports handlerReady: false and no routes.storage
instead of pointing at a path with nothing behind it.
See also
- API Overview — discovery, error handling, and protocol types
- Client SDK — service-aware TypeScript client that adapts to installed plugins