ObjectStackObjectStack

Plugin Endpoints

REST endpoints that become available when the corresponding plugin is installed — auth, workflow, automation, views, realtime, notifications, AI, i18n, and file storage.

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).

This page is a catalog of routes the platform serves for you — it is not about authoring an endpoint of your own. To publish your own URL as metadata, see Declarative Endpoints.

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

MethodEndpointDescription
POST/automation/:name/triggerTrigger an automation flow by name (legacy alias: /automation/trigger/:name). Failures answer real status codes, not a 200 wrapping an inner failure: 404 unknown flow, 409 FLOW_DISABLED, 422 FLOW_NO_START_NODE, 400 FLOW_FAILED for a run that ran and was rejected — see Run a flow via API

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.

MethodEndpointDescription
GET/ui/views/:objectList views for an object
GET/ui/views/:object/:viewIdGet a view definition
POST/ui/views/:objectCreate a new view
PATCH/ui/views/:object/:viewIdUpdate a view
DELETE/ui/views/:object/:viewIdDelete 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.

MethodEndpointDescription
POST/realtime/connectEstablish WebSocket/SSE connection
POST/realtime/disconnectClose connection
POST/realtime/subscribeSubscribe to channel
POST/realtime/unsubscribeUnsubscribe
PUT/realtime/presence/:channelSet presence
GET/realtime/presence/:channelGet channel presence

Notifications (/notifications) — Plugin Required

MethodEndpointDescription
GET/notificationsList notifications (inbox)
POST/notifications/readMark notifications as read (body: { ids: string[] })
POST/notifications/read/allMark 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:

MethodEndpointSDKDescription
POST/ai/chatai.chat / ai.chatStreamChat completion — JSON with stream: false, otherwise the Vercel UI Message Stream
POST/ai/chat/streamGeneric-SSE twin of /ai/chat: the same completion without the tool loop or persistence
POST/ai/completeai.completeText completion
GET/ai/modelsai.modelsModels this environment offers (ADR-0028)
GET/ai/statusActive adapter provenance (console diagnostics)
GET/ai/effective-modelResolved model ids and their source (console diagnostics)
POST / GET/ai/conversationsai.conversations.create / .listCreate / list conversations
GET / PATCH / DELETE/ai/conversations/:idai.conversations.get / .update / .deleteRead / update / delete
POST/ai/conversations/:id/messagesai.conversations.addMessageAppend 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

MethodEndpointDescription
GET/i18n/localesList available locales
GET/i18n/translations/:localeGet translation bundle
GET/i18n/labels/:object/:localeGet field labels

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.

MethodEndpointSDKDescription
POST/storage/upload/presignedstorage.getPresignedUrlStep 1 — mint an upload target for a new sys_file row
POST/storage/upload/completestorage.uploadStep 3 — commit the uploaded bytes
POST/storage/upload/chunkedstorage.initChunkedUploadOpen a chunked / resumable session
PUT/storage/upload/chunked/:uploadId/chunk/:chunkIndexstorage.uploadPartSend one chunk
POST/storage/upload/chunked/:uploadId/completestorage.completeChunkedUploadAssemble the parts
GET/storage/upload/chunked/:uploadId/progressstorage.resumeUploadRead session progress (first step of a resume)
GET/storage/files/:fileId/urlstorage.getDownloadUrlResolve a short-lived signed download URL
GET/storage/files/:fileIdStable 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 storage slot (spelled file-storage before #9683; that spelling stays a deprecated v17 alias) 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
  • Declarative Endpoints — declaring an endpoint of your own (apis:), the other half of this page's name

On this page