API Access
Generated REST APIs, authentication, and API keys for integrations.
Every object declared in the artifact is exposed through a generated REST API. The same endpoints power the UI, integrations, and customer ETL scripts — there is no separate "integration API" to maintain.
Base URL
https://<project-domain>/api/v1Notable surfaces:
| Path | Purpose |
|---|---|
/api/v1/data/<object> | Generated CRUD for each object (e.g. /api/v1/data/account) |
/api/v1/data/<object>/{id} | Read/update/delete a single record |
/api/v1/actions/<object>/<action> | Invoke a declarative action (POST) |
/api/v1/auth/* | Authentication (sign-in, sessions, OAuth, OIDC) |
/api/v1/meta/* | Metadata APIs (objects, fields, views) when enabled |
/api/v1/keys | Mint a show-once sys_api_key for the calling user (POST) |
/api/v1/batch | Transactional cross-object batch (POST) — atomic-only, every operation gated per object |
/api/v1/mcp | Model Context Protocol server over Streamable HTTP — on by default; disable with OS_MCP_SERVER_ENABLED=false |
/api/v1/health | Liveness/readiness probe (no auth) |
The prefix defaults to /api/v1 (the API basePath of /api plus the
version of v1) and is configurable on the ObjectOS stack.
Controlling what's exposed
API exposure is governed per object in the artifact, not at the gateway:
- Set
apiEnabled: falseon an object to keep it off the REST surface entirely (its routes 404). - Set an
apiMethodswhitelist (e.g.['get', 'list']) to make an object read-only or otherwise restrict which operations are reachable.
Both are enforced at the REST layer — see REST API → Restricting the API surface.
The same gates cover the transactional batch route (16.0):
POST /api/v1/batch validates its body, enforces apiEnabled /
apiMethods for every operation before opening the transaction,
requires ids for update/delete, and rejects unresolvable { $ref }
parent references. The route is atomic-only — atomic: false is
rejected with 400 BATCH_NOT_ATOMIC; non-atomic bulk stays on the
per-object createMany / updateMany endpoints.
Authentication options
Callers can authenticate in four ways:
| Method | Best for | How |
|---|---|---|
| Session cookie | Browser/UI traffic | Sign in through /api/v1/auth/*; cookies are scoped to the project hostname |
| Bearer access token | Mobile, SPA, short-lived server jobs | Exchange credentials at /api/v1/auth/sign-in/email and pass Authorization: Bearer <token> |
| API key | Server-to-server, ETL, long-lived integrations | Create a sys_api_key, pass it as a bearer token |
| OAuth 2.1 (MCP) | AI agents / MCP clients acting as a signed-in user | Self-serve authorization-code + PKCE flow against the deployment itself |
All three pass through the same AuthPlugin and resolve to a sys_user
context that the SecurityPlugin evaluates against permissions and
record access.
API keys
API keys are first-class sys_api_key records. The key value itself is
stored hashed — only the prefix and metadata remain queryable, so a
leaked key cannot be reconstructed from the database.
Issue a key in either of two ways:
-
Console — sign in to Console → API Keys as an administrator, choose the owning user, optionally set an expiration date, and copy the displayed secret once.
-
Self-serve endpoint —
POST /api/v1/keysmints a key for the authenticated caller and returns the raw secret exactly once. The key is pinned to the caller's own user, so it cannot be used to impersonate another identity:curl -X POST https://app.example.com/api/v1/keys \ -H "Authorization: Bearer <session-or-access-token>" \ -H "Content-Type: application/json" \ -d '{"name": "etl-pipeline"}' # → { "id": "...", "name": "etl-pipeline", "prefix": "os_pk_…", "key": "os_pk_live_…" }
Pass the key on subsequent requests as a bearer token, an x-api-key
header, or Authorization: ApiKey. The REST data and metadata APIs all
authenticate it through the same verifier as MCP, under the key owner's
permissions and record-level security:
curl https://app.example.com/api/v1/data/account \
-H "x-api-key: os_pk_live_…"To revoke a key, run the revoke_api_key action on the corresponding
sys_api_key record (also available in the Console UI). Revocation takes
effect immediately on the next request.
MCP clients & OAuth 2.1 (ObjectStack 13+)
Any OAuth-capable MCP client — Claude.ai custom connectors, Claude Desktop, Claude Code — can connect self-serve, without an admin minting API keys:
- Each deployment acts as its own authorization server (backed by the
embedded auth instance), with RFC 8414 / RFC 9728 discovery, dynamic
client registration (RFC 7591), authorization-code + PKCE, and
resource binding to
<origin>/api/v1/mcp. TLS is required (localhost exempt). - Users sign in as themselves; tool calls run under their own permissions and row-level security.
- OAuth scopes establish a hard permission ceiling
(ObjectStack 14.5):
effective_permission = scope_ceiling ∩ user_grants.data:readgrants read-only data access,data:writefull CRUD, andactions:executeis required for capability-gated business actions. Tokens missing a scope fail closed.
API keys remain a parallel track for CI and headless agents — x-api-key
and Authorization: Bearer osk_… continue to work unchanged.
Pagination, filtering, and sorting
Generated list endpoints accept the standard ObjectStack query parameters:
| Parameter | Meaning |
|---|---|
?limit= | Page size (subject to a server-side ceiling) |
?offset= or ?cursor= | Pagination |
?filter= | Server-side filtering using the ObjectQL filter syntax |
?sort= | Sort expression (e.g. -created_at) |
?fields= | Sparse field selection |
Refer to the framework documentation for the full filter grammar — the runtime grammar is the source of truth.
Rate limiting
Use the framework's RateLimiter primitive (or your ingress / API
gateway) to apply per-IP and per-identity limits. Recommended starting
buckets are documented in Production readiness.
CORS
If callers run in a browser on a different origin, configure CORS at the ingress or via the runtime's middleware. Do not combine wildcard origins with credentialed requests.
OpenAPI / discovery
The runtime can serve an OpenAPI document for the generated REST API when the corresponding capability is included in the image. Customer integrations should generate clients from the per-deployment OpenAPI document rather than hand-rolling URLs, because object names and generated routes follow the deployed artifact.
Discovery also advertises capabilities.transactionalBatch (16.0), so
clients can detect the atomic batch route declaratively at connect time
instead of probing for 404/405/501.