REST API
The HTTP surface ObjectOS generates from your metadata — where the product uses it, a few examples, and the full specification on objectstack.ai.
Full specification: REST and GraphQL APIs on objectstack.ai. This page is the summary: where ObjectOS uses it, and a few examples.
Every object you declare gets REST endpoints, every action becomes a POST,
and every flow can be started over HTTP. There is no separate API layer to
write or deploy. The base URL is https://<your-host>/api/v1, and the running
runtime describes itself at /api/v1/openapi.json (OpenAPI 3.0) and at
/api/v1 (service discovery).
Every route under /api/v1 needs a signed-in session or a bearer token unless
a declaration opens it to anonymous callers, as public forms
and share links do. Permissions are checked per route and per
record: object-, record- and field-level security apply to an API caller
exactly as they apply in the UI. See API Access
for keys and tokens.
Where ObjectOS uses it
| Surface | Routes | Used by |
|---|---|---|
| Data | /data/:object, /data/:object/:id, /data/:object/query | The generated UI, your integrations, imports and exports — queries are ObjectQL |
| Actions | /actions/:object/:action | Buttons in the UI, and agents when the action is exposed to AI (Actions) |
| Automation | /automation/:name/trigger, /automation/:name/runs | Starting a flow from another system, and reading its runs (Flows) |
| Metadata | /meta/:type/:name | Studio, which saves through it, and tooling that reads the running metadata |
| AI | /ai/chat, /ai/pending-actions | The AI Builder and its approval queue |
| Public forms | /forms/:slug | A form view published for anonymous respondents |
Examples
List open tickets, newest first:
GET /api/v1/data/support_ticket?where={"status":{"$eq":"open"}}&orderBy=-created_at&limit=20
Authorization: Bearer <token>{
"object": "support_ticket",
"records": [ { "id": "...", "subject": "...", "status": "open" } ],
"total": 137,
"hasMore": true
}Take an object off the API, or make it read-only, in its enable block:
import { ObjectSchema, Field } from '@objectstack/spec/data';
// A read-only reference object, never writable over the API:
ObjectSchema.create({
name: 'exchange_rate',
enable: { apiMethods: ['get', 'list'] },
fields: {
rate: Field.number({ label: 'Rate', scale: 6 }),
// ...more fields
},
})
// An internal object that the API never exposes:
ObjectSchema.create({
name: 'sync_cursor',
enable: { apiEnabled: false },
fields: {
cursor: Field.text({ label: 'Cursor' }),
// ...more fields
},
})Branch on the error code, never on the message:
{
"error": {
"code": "PERMISSION_DENIED",
"message": "Missing ai:approve on conversation 01H…",
"httpStatus": 403,
"details": { "permission": "ai:approve", "subject": "01H…" }
}
}Restricting the API surface
An object's enable block governs its generated endpoints. apiEnabled: false
omits the object from the REST surface entirely, so its routes answer 404.
apiMethods is a whitelist of get, list, create, update, delete and
bulk; any other operation is refused at the REST layer. Both are enforced
(ADR-0049); earlier versions parsed them without applying them.
Public forms
Answered without a session. A form view opts in through its sharing,
and all three keys are required: enabled: true, allowAnonymous: true and a
publicLink slug. Since ObjectStack 17.7 a form that sets only the last two
answers 404 FORM_NOT_FOUND — see
Views → Public forms.
GET /forms/:slug returns the form, POST /forms/:slug/submit submits it.
Share links
Also answered without a session: the token in the link is the authorization.
GET /share-links/:token/resolve returns the one record the link shares,
without the fields the link hides. A link limited to signed-in users answers
401 SIGN_IN_REQUIRED to a caller without a session, and a password-protected
one answers 401 NEEDS_PASSWORD until the password is sent. Creating, listing
and revoking links (POST /share-links, GET /share-links,
DELETE /share-links/:idOrToken) need a session — see
Share links: anyone with the link.
Flow status table
The trigger route, an action of type flow and a declared type: 'flow'
endpoint all answer the same statuses. Branch on the HTTP status, not on a
success flag inside the body:
| Status | Code | Means |
|---|---|---|
200 | — | The run dispatched. Its status is completed, paused (it waits on a screen, an approval or a timer), or refused (it reached an end node with outcome: 'refused'; the text is in refusalMessage) |
404 | RESOURCE_NOT_FOUND | No flow has that name |
409 | FLOW_DISABLED | The flow is switched off |
422 | FLOW_NO_START_NODE | The stored definition has no start node and cannot run |
400 | FLOW_FAILED | The run dispatched and failed; the details carry the flow's errorMessage |
Metadata — /api/v1/meta/*
/meta/:type/:name is the only address of a metadata item; percent-encode a
name that carries a slash (GET /api/v1/meta/lead/views%2Fall_leads). The
sectioned /meta/:type/:section/:name routes were retired in ObjectStack 17.3
and answer 404 ROUTE_NOT_FOUND. Saving needs an authoring capability, and
reading or saving datasource metadata needs manage_platform_settings.
Reports
There is no saved-report API. ObjectStack 17.5 removed /reports,
/reports/:id/run and /reports/:id/schedule, which now answer 404. A
report is metadata, read through /meta/report/:name, and its numbers come
from analytics (POST /analytics/query).
See also
- API Access — keys, tokens and who may call what
- ObjectQL — the query format
/data/*accepts - Security — what every route checks before it runs