REST API
The REST surface lives under /v1/*. Everything the Console does, you can do programmatically — the Console itself is just an API consumer.
Base URL
Section titled “Base URL”| Environment | Base URL |
|---|---|
| Hosted (production) | https://app.openma.dev |
| Hosted (staging) | https://app.staging.openma.dev |
| Self-host | Whatever you set in apps/main/wrangler.jsonc → routes |
Authentication
Section titled “Authentication”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.
export OMA_BASE_URL="https://app.openma.dev"export OMA_API_KEY="oma_..."Endpoint groups
Section titled “Endpoint groups”| Group | Path | Purpose |
|---|---|---|
| Agents | /v1/agents | Create / read / update / archive agent configurations. Versioned — every update bumps the version. |
| Sessions | /v1/sessions | Create sessions, list, fetch event log. Live events via SSE. |
| Environments | /v1/environments | Sandbox templates: packages, network rules, base image. |
| Skills | /v1/skills | Built-in + custom skill catalog. Custom skill files via R2-backed upload. |
| Vaults | /v1/vaults | Credential stores (bearer tokens, OAuth, env vars). |
| Memory Stores | /v1/memory_stores | Per-agent semantic memory. |
| Files | /v1/files | Per-session file storage. |
| Model Cards | /v1/model_cards | Custom model provider configuration. |
| Integrations | /v1/integrations | Linear / GitHub / Slack install state, webhooks, publications. |
| Evals | /v1/evals | Evaluation framework. |
| API Keys | /v1/api_keys | Manage personal access tokens. |
| OAuth | /v1/oauth | Third-party OAuth flows for the Console UI. |
A full endpoint-by-endpoint reference lives at Reference → API Endpoints.
Common patterns
Section titled “Common patterns”Create an agent
Section titled “Create an agent”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"}] }'Create a session
Section titled “Create a session”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.
Stream session events (SSE)
Section titled “Stream session events (SSE)”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:
| Event | When |
|---|---|
user.message | Echo of the turn you posted |
session.status_running / _idle / _rescheduled / _terminated / error | Lifecycle |
agent.thinking | Extended-thinking block (whole) |
agent.message | Assistant message (whole) |
agent.tool_use / agent.mcp_tool_use / agent.custom_tool_use | Tool call (whole) |
agent.tool_result / agent.mcp_tool_result | Tool 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.
?include=chunks — OMA extension events
Section titled “?include=chunks — OMA extension events”Adds finer-grained streaming + extra observability. Required for token-by-token UI rendering.
| Event | When |
|---|---|
agent.message_stream_start / _chunk / _stream_end | Live assistant-text deltas — chunk events carry { message_id, delta } |
agent.thinking_stream_start / _chunk / _stream_end | Live extended-thinking deltas — chunk events carry { thinking_id, delta } |
agent.tool_use_input_stream_start / _chunk / _stream_end | Live tool-input JSON streaming — chunk events carry { tool_use_id, delta } |
system.user_message_pending / _promoted / _cancelled | Pending-queue lifecycle (outbox UX) |
session.warning | Recovery scan reconciled an interrupted stream / orphan tool_use after a runtime restart |
span.model_first_token / span.compaction_summarize_* / span.wakeup_scheduled / aux.model_call | Extra 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 tailingLast-Event-ID: <seq>— replay events withseq > Nonly (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.
# Console-style: full history + token-level streamingcurl -N "$OMA_BASE_URL/v1/sessions/sess_xyz/events/stream?include=chunks&replay=1" \ -H "x-api-key: $OMA_API_KEY"Send a turn (chatbot one-shot)
Section titled “Send a turn (chatbot one-shot)”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).
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):
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.
Upload a file to a session
Section titled “Upload a file to a session”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.
Errors
Section titled “Errors”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.
Rate limits
Section titled “Rate limits”Per-tenant. Limits and current usage are surfaced in response headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix epoch seconds).