Skip to content

REST API

The REST surface lives under /v1/*. Everything the Console does, you can do programmatically — the Console itself is just an API consumer.

EnvironmentBase URL
Hosted (production)https://app.openma.dev
Hosted (staging)https://app.staging.openma.dev
Self-hostWhatever you set in apps/main/wrangler.jsonc → routes

Two auth schemes:

API key — for server-to-server and CLI use. Pass as the x-api-key header (x-api-key: oma_...). Create on the Console API Keys page.

Session cookie — what the Console UI uses (better-auth-issued). Not normally what you’ll integrate against unless you’re embedding openma in a browser app.

Terminal window
export OMA_BASE_URL="https://app.openma.dev"
export OMA_API_KEY="oma_..."
GroupPathPurpose
Agents/v1/agentsCreate / read / update / archive agent configurations. Versioned — every update bumps the version.
Sessions/v1/sessionsCreate sessions, list, fetch event log. Live events via SSE.
Environments/v1/environmentsSandbox templates: packages, network rules, base image.
Skills/v1/skillsBuilt-in + custom skill catalog. Custom skill files via R2-backed upload.
Vaults/v1/vaultsCredential stores (bearer tokens, OAuth, env vars).
Memory Stores/v1/memory_storesPer-agent semantic memory.
Files/v1/filesPer-session file storage.
Model Cards/v1/model_cardsCustom model provider configuration.
Integrations/v1/integrationsLinear / GitHub / Slack install state, webhooks, publications.
Evals/v1/evalsEvaluation framework.
API Keys/v1/api_keysManage personal access tokens.
OAuth/v1/oauthThird-party OAuth flows for the Console UI.

A full endpoint-by-endpoint reference lives at Reference → API Endpoints.

Terminal window
curl -X POST "$OMA_BASE_URL/v1/agents" \
-H "x-api-key: $OMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "research-agent",
"model": "claude-sonnet-4-6",
"system": "You are a research assistant.",
"tools": [{"type": "agent_toolset_20260401"}]
}'
Terminal window
curl -X POST "$OMA_BASE_URL/v1/sessions" \
-H "x-api-key: $OMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": "agent_abc123",
"environment_id": "env_lvqed06glbi3ent0",
"title": "research session"
}'

The response includes the new session’s id (e.g. sess_xyz). Send the first user turn separately — see “Send a turn” below.

Terminal window
curl -N "$OMA_BASE_URL/v1/sessions/sess_xyz/events/stream" \
-H "x-api-key: $OMA_API_KEY"

The connection stays open. Each data: line is a single JSON event.

Default behavior is Anthropic-spec-aligned (Managed Agents events) — only events emitted after open are delivered, and only the spec event types appear:

EventWhen
user.messageEcho of the turn you posted
session.status_running / _idle / _rescheduled / _terminated / errorLifecycle
agent.thinkingExtended-thinking block (whole)
agent.messageAssistant message (whole)
agent.tool_use / agent.mcp_tool_use / agent.custom_tool_useTool call (whole)
agent.tool_result / agent.mcp_tool_resultTool execution result
agent.thread_* / session.thread_*Multiagent coordination
span.model_request_start / _end / span.outcome_evaluation_*Observability

Use the official @anthropic-ai/sdk directly — it parses these unchanged.

Adds finer-grained streaming + extra observability. Required for token-by-token UI rendering.

EventWhen
agent.message_stream_start / _chunk / _stream_endLive assistant-text deltas — chunk events carry { message_id, delta }
agent.thinking_stream_start / _chunk / _stream_endLive extended-thinking deltas — chunk events carry { thinking_id, delta }
agent.tool_use_input_stream_start / _chunk / _stream_endLive tool-input JSON streaming — chunk events carry { tool_use_id, delta }
system.user_message_pending / _promoted / _cancelledPending-queue lifecycle (outbox UX)
session.warningRecovery scan reconciled an interrupted stream / orphan tool_use after a runtime restart
span.model_first_token / span.compaction_summarize_* / span.wakeup_scheduled / aux.model_callExtra observability

Stream-lifecycle events (*_stream_start, *_chunk, *_stream_end) are broadcast over the wire but not persisted to the events log — the canonical event with the same correlation id is the source of truth. Replay clients only see the canonical events; live subscribers see both.

?replay=1 and Last-Event-ID — history replay

Section titled “?replay=1 and Last-Event-ID — history replay”

Default behavior matches Anthropic: no replay, only events after open. To bootstrap clients with persisted history:

  • ?replay=1 — replay the full persisted log on connect, then continue tailing
  • Last-Event-ID: <seq> — replay events with seq > N only (SSE-native resume; browsers send this automatically on reconnect)

Both compose with ?include=chunks. For the open-then-list-then-dedupe pattern (Anthropic’s recommended way to recover history without replay), see GET /v1/sessions/:id/events for the JSON-paginated history.

Terminal window
# Console-style: full history + token-level streaming
curl -N "$OMA_BASE_URL/v1/sessions/sess_xyz/events/stream?include=chunks&replay=1" \
-H "x-api-key: $OMA_API_KEY"

OMA-only convenience endpoint (no Anthropic-spec equivalent). Posts the user message and streams the response in one HTTP call. Always emits chunk events. The connection auto-closes when the turn finishes (session.status_idle).

Terminal window
curl -N -X POST "$OMA_BASE_URL/v1/sessions/sess_xyz/messages" \
-H "x-api-key: $OMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "Now also include the integrations." }'

content accepts a plain string (wrapped as a single text block) or a ContentBlock[] array (mix text + image + document). Returns text/event-stream.

Send a turn into a session that’s already streaming elsewhere

Section titled “Send a turn into a session that’s already streaming elsewhere”

When you want fire-and-forget instead (e.g. you have a separate SSE consumer):

Terminal window
curl -X POST "$OMA_BASE_URL/v1/sessions/sess_xyz/events" \
-H "x-api-key: $OMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "events": [{ "type": "user.message", "content": [{ "type": "text", "text": "Now also include the integrations." }] }] }'

Returns 202 with no body — the SSE stream you already have open will receive the response events.

Terminal window
curl -X POST "$OMA_BASE_URL/v1/files" \
-H "x-api-key: $OMA_API_KEY" \
-F "session_id=sess_xyz" \
-F "file=@./report.pdf"

The file lands in R2 and is mounted into the sandbox at /home/user/files/<id>/report.pdf.

Standard HTTP status codes. Body shape:

{
"error": {
"type": "invalid_request",
"message": "agent_id is required"
}
}

Common types: invalid_request, not_found, unauthorized, forbidden, rate_limited, internal_error.

Per-tenant. Limits and current usage are surfaced in response headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix epoch seconds).