ObjectStackObjectStack

API Overview

How ObjectStack generates its API surface from metadata — discovery, error handling wire formats, protocol types, and a map of the API & SDK docs.

API Overview

ObjectStack generates its entire API surface — REST endpoints, realtime protocols, and the client SDK — from your metadata: define an object once and its endpoints exist. This module documents those surfaces and how to consume them.

ObjectStack exposes a fully typed REST API. All endpoints use JSON request/response bodies. The API is service-driven — routes are only available when the corresponding plugin is installed. Use the Discovery endpoint to determine what services are available at runtime.

Surfaces at a glance

SurfaceStatus in this repo
REST✅ Auto-generated from the protocol (@objectstack/rest) — CRUD, query, batch, metadata, packages
Realtime⚠️ In-process pub/sub service (@objectstack/service-realtime, single-instance); the /realtime/* REST routes and WebSocket/SSE transport are plugin-provided — none ships in the open framework
MCP✅ Non-system objects exposed automatically as Model Context Protocol tools; actions additionally require the author's ai.exposed opt-in — every call is gated by the caller's permissions/RLS (AI module)
GraphQL⚠️ Route is wired but bring-your-own service: /graphql returns 501 unless an implementation of the IGraphQLService contract is registered — none ships in the open framework
OData⚠️ Vocabulary only: REST list endpoints accept OData-style operators (e.g. $top), but there is no standalone OData endpoint

Base URL: Configurable, defaults to /api/v1. All paths below are relative to the base URL.

Your app as an MCP server

REST and GraphQL are how code consumes your app. MCP is how AI consumes it. Because every object and action is typed metadata, ObjectStack can expose the whole app as a Model Context Protocol server — so an AI client (Claude Code, Claude Desktop, Cursor, a local model) can inspect and operate the app you built, under the same permissions and RLS as the UI. It is served at /api/v1/mcp by default — set OS_MCP_SERVER_ENABLED=false to opt out (see environment variables).

The generated tools mirror the surfaces you already defined: list_objects / describe_object (discover the schema), query_records / get_record (read), create_record / update_record / delete_record (write), and list_actions / run_action (invoke your business actions by name). Every call runs as the caller — RBAC, RLS, and field-level security all apply.

sequenceDiagram
    participant AI as AI client (Claude / Cursor)
    participant MCP as MCP server (@objectstack/mcp)
    participant Eng as Action / ObjectQL engine
    participant Sec as RBAC · RLS · FLS
    participant DB as Data

    AI->>MCP: run_action("resolve", { recordId })
    MCP->>Eng: dispatch as the caller's principal
    Eng->>Sec: check permissions on this record
    Sec-->>Eng: allowed (or denied — fail-closed)
    Eng->>DB: apply the change
    DB-->>AI: result, same as the Console would return

See Actions as Tools for the run_action bridge and the MCP reference for binding external MCP servers into your agents.

Authentication

Every REST call runs as a principal — anonymous requests only see what your permission model grants anonymous users. Two ways to authenticate:

  • Session cookie (browsers, quick local tests): POST /api/v1/auth/sign-in/email with { "email": "…", "password": "…" } sets the session cookie — reuse it with curl -c cookies.txt / -b cookies.txt. On a fresh dev database the seeded admin is admin@objectos.ai / admin123.
  • API key (scripts, CI, headless agents): mint one with POST /api/v1/keys (the key is shown once), or from Setup → Connect an Agent in the Console. Send it as x-api-key: osk_… or Authorization: Bearer osk_….

See Authentication for the full identity surface (OAuth flows, sessions, providers) and Plugin Endpoints for the auth route catalog.

What's in this module

Spec: HTTP API, Real-Time Protocols

Schema reference: API


Discovery

The discovery endpoint is the entry point for all clients. It returns the API version, available routes, service capabilities, and per-service status.

GET /api/v1

Returns the full discovery manifest.

Response:

{
  "version": "1.0.0",
  "apiName": "ObjectStack API",
  "routes": {
    "data": "/api/v1/data",
    "metadata": "/api/v1/meta",
    "analytics": "/api/v1/analytics"
  },
  "services": {
    "metadata": {
      "enabled": true,
      "status": "available",
      "route": "/api/v1/meta",
      "provider": "objectql"
    },
    "data": { "enabled": true, "status": "available", "route": "/api/v1/data", "provider": "objectql" },
    "analytics": { "enabled": true, "status": "available", "route": "/api/v1/analytics", "provider": "objectql" },
    "auth": { "enabled": false, "status": "unavailable", "message": "Install plugin-auth to enable" }
  },
  "capabilities": {
    "feed": { "enabled": false },
    "automation": { "enabled": false },
    "search": { "enabled": false }
  }
}

Disabled/uninstalled route keys (e.g. auth, workflow) are omitted from routes entirely rather than set to null; check services to tell "not installed" apart from "installed but not yet mounted here."

GET /.well-known/objectstack

Served by the runtime dispatcher (@objectstack/runtime), not @objectstack/rest — its body is wrapped as { "data": { ... } } and includes fields (name, environment, features, locale) that the @objectstack/rest-served /api/v1 response above does not. The client SDK's connect() tries /api/v1/discovery first and falls back to this endpoint, unwrapping either body.data or the bare body.

Service Status Values: available (fully operational), registered (route declared but handler unverified — may return 501), degraded (partial functionality), unavailable (not installed), stub (placeholder that throws errors)


Error Handling

Error responses depend on which HTTP server is in front of the kernel. There are two wire formats in use today.

Kernel REST server (@objectstack/rest) emits a string error message plus a SCREAMING_SNAKE code:

{
  "error": "Record not found: account/123",
  "code": "RECORD_NOT_FOUND"
}

Validation failures additionally include a fields array (one entry per invalid field). Common codes emitted by the kernel REST server:

CodeHTTPDescription
VALIDATION_FAILED400Input validation failed (includes fields)
PERMISSION_DENIED403Insufficient permissions
RECORD_NOT_FOUND404Resource does not exist
CONCURRENT_UPDATE409Record was modified by another user

Runtime dispatcher (@objectstack/runtime) wraps errors in the { success: false, ... } envelope, where code is the numeric HTTP status:

{
  "success": false,
  "error": {
    "message": "Record not found: account/123",
    "code": 404,
    "details": {}
  }
}

The richer ErrorResponseSchema in @objectstack/spec/api (with category, retryable, etc.) is the aspirational spec envelope, not the current wire format. Its category values are drawn from the ErrorCategory enum: validation, authentication, authorization, not_found, conflict, rate_limit, server, external, maintenance.


Protocol Types (Zod)

All request/response schemas are defined as Zod schemas in @objectstack/spec/api and can be used for both runtime validation and TypeScript type inference.

import { 
  FindDataRequestSchema, 
  FindDataResponseSchema,
  type FindDataRequest,
  type FindDataResponse,
} from '@objectstack/spec/api';

// Runtime validation
const request = FindDataRequestSchema.parse({ object: 'account', query: { ... } });

// TypeScript type
const response: FindDataResponse = await protocol.findData(request);

See the Protocol Reference for the full list of protocol methods and their Zod schemas.


Next Steps

On this page