ObjectStackObjectStack

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

MethodEndpointDescription
POST/automation/:name/triggerTrigger 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.

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

GraphQL (/graphql) — Plugin Required

MethodEndpointDescription
POST/graphqlExecute 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.

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

On this page