Skip to main content
Meerkat ships a REST server for running and managing agent sessions over HTTP. This is the best fit if you want a simple, language-agnostic API for the Meerkat core.

Getting started

1

Configure provider credentials

For the simplest environment-backed setup, export the key for your chosen provider before launching the server. Realm auth profiles and bindings are the persistent alternative:
2

Start the server

In that same configured terminal, start the installed binary:
3

Send a request

Leave the server running and use another terminal for curl. Select an Anthropic model to match the key above:

Runtime scope

rkat-rest accepts:
  • --realm <id>
  • --isolated
  • --instance <id>
  • --realm-backend <sqlite|jsonl|memory>
  • --state-root <path>
  • --context-root <path>
  • --user-config-root <path>
  • --expose-paths
If --realm is omitted, the server creates a new isolated opaque realm (realm-...). --realm-backend is a creation hint only; after first open, realm_manifest.json is authoritative.
The REST server has no general inbound HTTP authentication layer. The auth/* routes manage outbound provider credentials; they do not authenticate callers to this API. Keep the listener on loopback or put it behind an authenticated, encrypted reverse proxy. The optional webhook-secret check protects only POST /sessions/{id}/external-events and POST /sessions/{id}/peer-response-terminal, not the other routes.

Endpoint overview

This overview follows the generated OpenAPI artifact at artifacts/schemas/rest-openapi.json. Helper requests (/mob/{id}/spawn-helper, /mob/{id}/fork-helper) require result_label and max_text_bytes. Spawn and fork responses are exact-operation results with required output, tokens_used, agent_identity, member_ref, bounded_result, session_id, usage, turns, and tool_calls; retirement_error is present only when cleanup debt remains after the certified result was captured.
Schedule endpoints return the same flattened public Schedule contract as JSON-RPC. Planning, labels, and created/updated timestamps are top-level; there is no nested config and persisted machine_state is not public.
WorkGraph goal and attention mutation routes require trusted in-process host/session authority and are not part of the public REST catalog. Approvals, durable jobs and monitors, stable artifact record/download methods, and low-latency live channels are not REST routes. Those operator surfaces are available through JSON-RPC and the generated SDKs. REST history can carry blob references, but REST does not expose blob or artifact download paths. Multi-host mob administration (host bind/revoke, hard-cancel, member live channels, grant management) is deliberately NOT served by REST — REST carries observation GETs only. The complete admin family lives on JSON-RPC and the Python/TypeScript SDKs; host binding/revoke, grants, and live control also have CLI verbs, while hard cancel is RPC/SDK-only. Durable member role migration is also outside the public REST contract. The shared OpenAPI component catalog includes private member-host bridge schemas, including MaterializeLaunchMode, because components are generated from shared wire types. No REST path accepts resume_from_role; only a trusted Rust host or the private member-host materialization protocol can issue that one-shot declaration. The paths object, not component reachability, defines REST exposure.
Generated images are not returned as inline bytes in history. GET /sessions/{id}/history returns assistant image blocks with image_id, blob_ref, dimensions, and metadata; fetch image bytes through the blob/artifact surface exposed by the runtime-backed RPC/SDK path.
REST keeps observation and helper endpoints for mobs, but typed lifecycle/control for app hosts lives on the canonical RPC/SDK mob/* surface. Inside running sessions, mob capability is still exposed by composing meerkat-mob-mcp (MobMcpState + AgentMobToolSurfaceFactory) into SessionBuildOptions.mob_tools in the host runtime. external_tools remains reserved for callback and MCP-backed dispatchers.
WorkGraph REST endpoints are observability/operator lookup, including goal-status and attention-list reads. Agents create, claim, update, link, evidence, close, reassign attention, and escalate policy through WorkGraph tools inside sessions; goal and attention mutators stay on trusted in-process host authority.

Request cancellation

REST request cancellation is opt-in and request-ID based.
  • send X-Meerkat-Request-Id: <id> on POST /sessions or POST /sessions/{id}/messages
  • call POST /requests/{request_id}/cancel to cancel uncommitted in-flight work
  • duplicate in-flight request IDs are rejected
Cancellation only affects uncommitted work:
  • pre-start / pre-commit work may return cancelled
  • committed success is not rewritten to cancellation
  • post-commit create failures still return session identity and remain resumable

Configuration

REST configuration is realm-scoped:
  • macOS: ~/Library/Application Support/meerkat/realms/<realm>/config.toml
  • Linux: ~/.local/share/meerkat/realms/<realm>/config.toml
  • Windows: %APPDATA%\\meerkat\\realms\\<realm>\\config.toml
Each realm also has realm_manifest.json (backend pinning) and config_state.json (generation CAS state).Key sections:
API keys are provided via environment variables:
  • ANTHROPIC_API_KEY
  • OPENAI_API_KEY
  • GOOGLE_API_KEY

Endpoints

POST /help

Ask Meerkat usage help with the embedded platform skill.
Request
Returns a HelpResponse with the answer text and any plan metadata requested by the input.

POST /sessions

Create and run a new session.

Request fields

ContentInput (string | ContentBlock[])
required
The user prompt: a string or a typed content-block array, for example [{"type":"text","text":"Hello"}]. Media blocks depend on the selected model/provider; see model capabilities.
ContentInput[] | null
default:"null"
Host-attached injected context for the first turn. Each entry materializes as a separate typed injected-context user-channel message immediately before the first turn’s user message, in order; injected context is excluded from semantic-memory indexing. Older REST servers silently ignore this field (existing REST version-skew posture).
string | null
default:"null"
Non-empty, exact host-regenerated facts for the first logical turn. Whitespace is significant. Meerkat persists the text only with the pending runtime input for crash retry and projects it as a request-local user-channel context message immediately before that turn’s conversational user message. It never enters Session history, compaction, or extraction requests.
SystemPromptOverride
default:"inherit"
Typed per-request system-prompt policy: omit/null to inherit, a string to set an explicit prompt, or {"action": "disable"} to suppress every prompt source.
string | null
default:"config default"
Model name (e.g. "claude-opus-4-8", "gpt-5.5").
string | null
default:"inferred from model"
Provider: "anthropic", "openai", "gemini", "self_hosted", "other".
WireAuthBindingRef | null
default:"null"
Optional structural credential selector, for example {"realm":"prod","binding":"anthropic","profile":"claude_oauth"}. realm and binding are required when supplied; profile is optional. Configure the referenced realm, binding, and auth profile first. Omission/null lets the create resolver select credentials. This is an object, not the CLI’s colon-delimited string; provenance is server-owned.
u32 | null
default:"config default"
Max tokens per turn.
OutputSchema | null
default:"null"
JSON schema for structured output extraction (wrapper or raw schema).
u32 | null
default:"null"
Max retries for structured output validation. null/omitted uses the server default on create and inherits the persisted session value on continue.
bool
default:"false"
Enable verbose event logging (server-side).
bool | null
default:"null"
Keep session alive after turn for comms. On create, null/omitted uses the create default (false), true enables, and false explicitly disables. Requires comms_name when enabled.
string | null
default:"null"
Agent name for inter-agent communication.
object | null
default:"null"
Friendly metadata for peer discovery (name, description, labels).
HookRunOverrides | null
default:"null"
Run-scoped hook overrides (see Hooks).
bool | null
default:"null (factory default)"
Enable built-in tools (task management, etc.). Omit to use factory defaults.
bool | null
default:"null (factory default)"
Enable shell tool (requires enable_builtins). Omit to use factory defaults.
bool | null
default:"null (factory default)"
Enable semantic memory. Omit to use factory defaults.
bool | null
default:"null (factory default)"
Override schedule tools for this session. Omit to use factory defaults.
bool | null
default:"null (factory default)"
Override WorkGraph tools for this session. Omit to use factory defaults.

Response fields

string
UUID of the created session.
string
The agent’s response text.
u32
Number of LLM calls made.
u32
Number of tool calls executed.
WireUsage
Token usage breakdown.
JSON (optional)
Parsed structured output when extraction succeeds. Omitted when absent.
ExtractionError (optional)
Post-turn extraction failure details: last_output is the committed main-turn assistant text that extraction attempted to transform, attempts is the number of extraction attempts made, and reason describes the failure. Omitted when absent.
SchemaWarning[] (optional)
Schema compatibility warnings per provider. Omitted when absent.
A successful response can contain committed text even if post-turn structured-output extraction fails. Inspect both structured_output and extraction_error; missing structured output does not prove extraction was never requested. These optional REST fields are omitted when absent, not filled with null.

GET /sessions//history

Read committed transcript history for a session without changing the lightweight metadata shape of GET /sessions/{id}. Query parameters:
  • offset — skip this many messages from the start of the transcript
  • limit — cap the number of returned messages
History is returned oldest-to-newest and reflects the last committed session snapshot only.

POST /sessions/{id}/system_context

Append one ordinary durable ordered System message at the admitted transcript boundary. content uses the CoreRenderable tagged shape, and the optional idempotency_key makes an exact caller retry safe.
Request
Response

POST /sessions/{id}/system_prompt

Replace one keyed system-prompt slot with version CAS. The first explicit adoption supplies target_message_index for an existing unversioned System row and omits expected_version. Later updates omit the target and provide the latest expected_version. expected_parent_revision optionally adds a transcript-head CAS.
Request
The result contains session_id, key, the new version, message_index, status (applied or duplicate), transcript_revision, and an optional rewrite commit. Mob-owned system prompts must be updated through their mob owner and are rejected on this session route.

GET /sessions

List sessions. Supports optional label filters via query parameters.
Response
Each row is a WireSessionSummary. created_at and updated_at are Unix seconds. is_active is the reported session activity flag, not a full runtime-state enum; use the status endpoint for runtime state. Optional session_ref and non-empty labels may also be present. The separate GET /sessions/{id} detail response retains its RFC3339 timestamp strings.

POST /sessions/{id}/messages

Continue an existing session.

Request fields

string
required
Session ID (must match the path {id}).
ContentInput (string | ContentBlock[])
required
Follow-up prompt as text or typed content blocks, for example [{"type":"text","text":"Continue"}]. Media support is model/provider dependent; see model capabilities.
ContentInput[] | null
default:"null"
Host-attached injected context for this turn. Each entry materializes as a separate typed injected-context user-channel message immediately before the turn’s user message, in order; injected context is excluded from semantic-memory indexing. Older REST servers silently ignore this field (existing REST version-skew posture).
string | null
default:"null"
Non-empty, exact host-regenerated facts for this logical turn. Whitespace is significant. The value is available to every foreground provider call and retry in the turn, persists only while the runtime input is pending, and never becomes a Session message.
string | null
default:"null"
Override the system prompt.
string | null
default:"from session"
Model override for this turn. On materialized sessions this hot-swaps the LLM client for the remainder of the session.
string | null
default:"from session"
Provider override for this turn. Typically inferred from model. Used with model for mid-session provider switching.
u32 | null
default:"from session"
Max tokens override for this turn.
OutputSchema | null
default:"null"
Structured output schema for this turn.
u32 | null
default:"null"
Max retries for structured output validation. Omit / null to inherit the persisted session value on continue.
bool
default:"false"
Enable verbose event logging.
bool | null
default:"null"
Keep-alive override for this turn. null = inherit persisted session intent, true = enable, false = disable.
string | null
default:"from session"
Agent name for comms.
HookRunOverrides | null
default:"null"
Run-scoped hook overrides.

Response fields

Same shape as POST /sessions.

POST /sessions/{id}/interrupt

Interrupt an in-flight turn. No-op if the session is idle.
Response
interrupted is derived from the typed result. The public classifier also reports "interrupted"/true for an existing idle session’s no-op; it is not proof that a live turn was cancelled. The shared result type also has "staged_noop"/false for staged sessions on RPC’s deferred-create path; REST does not expose deferred creation. Typed failures remain HTTP errors, not successful interrupt results.

POST /requests/{request_id}/cancel

Cancel an uncommitted in-flight request that previously supplied X-Meerkat-Request-Id.
Response
Returns 200 with {"cancelled": false, "reason": "already_terminal"} when the tracked request already published or completed. Returns 404 only when the request ID is unknown.

DELETE /sessions/{id}

Archive (remove) a session.
Response
Returns 404 if the session is not found.

POST /sessions/{id}/external-events

Queue an external event through the runtime-backed admission path.
When RKAT_WEBHOOK_SECRET is configured on the server, this route requires the X-Webhook-Secret header. Admission queues the event without waking an idle session. Correlated terminal peer responses use POST /sessions/{id}/peer-response-terminal, not this generic event route.
string
required
Must be "generic_json". The "peer_response_terminal" tag is reserved and rejected here.
string
required
Event classification, for example "monitor.alert".
JSON
required
Application payload inside the tagged envelope, not a flat request body.
WireContentBlock[] | null
default:"null"
Optional typed content blocks accompanying the event.
string
Webhook secret for authentication. Required when RKAT_WEBHOOK_SECRET env var is set on the server. Compared using constant-time equality (subtle::ConstantTimeEq).

POST /sessions/{id}/peer-response-terminal

Admit a terminal peer response through the typed runtime ingress. Like external-events, this route requires X-Webhook-Secret when the server has RKAT_WEBHOOK_SECRET configured; a missing or invalid secret returns HTTP 401. The following example assumes that secret is enabled:
With webhook secret
string
Required when RKAT_WEBHOOK_SECRET is set on the server; omit only when webhook-secret authentication is disabled.
string
required
Canonical peer routing ID. Display names are not accepted as routing identity.
string
Optional presentation label.
string
required
Peer correlation ID for the request this terminal response completes.
string
required
Terminal response status: "completed", "failed", or "cancelled".
any JSON
required
Peer-returned terminal payload.

GET /sessions/{id}

Fetch session metadata and usage.
Response

GET /sessions/{id}/events

Server-Sent Events (SSE) stream for real-time updates. Event types: The live SSE subscription is bounded and best-effort. Upstream stream_truncated envelopes can be forwarded, but the REST transport’s own broadcast receiver can also lose events without a marker or dropped count. Do not infer completeness from the absence of a lag marker. Built-in realm-backed persistent sessions can feed an optional durable audit projector through a separate unbounded queue, so SSE lag does not drop its input. That audit log is asynchronous derived state, not a guaranteed complete replay. Reconnect and reconcile from session history/status, or use the JSON-RPC events/* methods when that optional replay surface is installed and healthy.

GET /skills

List all skills with provenance information. Returns active and shadowed entries.
Response
Returns 404 if skills are not enabled.

GET /health

Returns "ok" (HTTP 200). Use for liveness checks.

GET /runtime/host_info

Returns the read-only RuntimeHostInfo projection: process identity, host ID scope, realm and endpoint metadata, feature flags, and the same health payload as GET /runtime/health. It does not enroll a host or grant placement authority.

GET /runtime/capabilities

Returns RuntimeHostCapabilities, a contract version plus boolean RuntimeHostFeatureFlags. These host feature flags are distinct from the generic status entries returned by GET /capabilities.

GET /runtime/health

Returns RuntimeHostHealth with status and a checks map. The declared dimensions are jobs, session_liveness, session_durability, session_runtime_loop, and session_run_start. REST measures the four session dimensions. It has no detached-job service probe, so it reports unmeasured:jobs as a coverage marker and leaves that marker out of the status rollup. A plain dimension key is a measured result. unreadable:<dimension> means the probe ran but could not obtain a reading and rolls the overall status up to at least degraded. unmeasured:<dimension> means this surface has no probe and does not by itself make a healthy host permanently degraded.

GET /models/catalog

Return the curated model catalog with provider profiles, capability metadata, and parameter schemas.
Response (abbreviated)
The catalog is resolved from built-in model metadata plus config-backed provider/server entries. Each provider entry includes the default model and a list of models with their capabilities and parameter schemas.

GET /capabilities

Returns runtime capabilities with status resolved against config.
Response (abbreviated)
The full response includes all registered capabilities from the running build, including sessions, streaming, structured_output, hooks, builtins, shell, comms, memory_store, schedule, work_graph, session_store, session_compaction, skills, and mcp_live when their owning crates are linked. See the JSON-RPC API for the corresponding RPC surface.

GET /config

Returns a realm config envelope:
  • config
  • generation
  • realm_id
  • instance_id
  • backend
  • resolved_paths

PUT /config

Replaces config. Accepted request forms:
  • Direct config object (compat)
  • Wrapped form with CAS:
    • { "config": <Config>, "expected_generation": <u64|null> }

PATCH /config

Applies RFC 7396 merge patch. Accepted request forms:
  • Direct patch object (compat)
  • Wrapped form with CAS:
    • { "patch": <JSON>, "expected_generation": <u64|null> }
If expected_generation is stale, the server returns 400 with a generation-conflict message.

GET /auth/profiles

List the auth profiles, backend profiles, and provider bindings for a realm.
string
required
Realm to read.
string | null
Optional profile selector accepted by the shared auth query shape.
Response

POST /auth/profiles

Store credentials for an existing binding-scoped auth profile. The binding must resolve to an auth profile whose source is managed_store; inline, env, external resolver, platform-default, command, and file-descriptor sources are configured outside this endpoint.
Request
Response (201 Created)
string
required
Realm containing the binding.
string
required
Binding whose configured auth profile will receive the stored credential.
string | null
Optional explicit profile override for the binding.
string
required
Provider expected from the resolved auth profile.
string
required
Stored-secret method: "api_key", "azure_api_key", or "static_bearer".
string
required
Secret material to persist in the token store.

GET /auth/bindings/{binding_id}

Read the auth profile resolved by a binding.
string
required
Binding to resolve.
string
required
Realm containing the binding.
string | null
Optional explicit profile override for the binding.

DELETE /auth/bindings/{binding_id}

Clear stored credentials for the resolved binding-scoped auth profile.
string
required
Binding whose stored credentials should be cleared.
string
required
Realm containing the binding.
string | null
Optional explicit profile override for the binding.

POST /auth/bindings/{binding_id}/test

Resolve a binding through the provider registry and report whether credential material or a dynamic authorizer is available.
Request
string
required
Binding to test.
string
required
Realm containing the binding.
string | null
Optional explicit profile override for the binding.

POST /auth/login/start

Begin a loopback OAuth login. The server owns the OAuth state and PKCE verifier.
Request
string
required
Configured credential-owner realm; the server’s active realm is not an automatic substitute.
string
required
Configured binding to receive the OAuth credentials.
string | null
Optional explicit auth profile override.
Start and complete must address the same configured credential owner/binding/profile. See authentication setup.

POST /auth/login/complete

Complete a loopback OAuth login and store the resulting tokens under an explicit binding-scoped AuthBindingRef.
Request
string
required
Realm containing the binding.
string
required
Binding that owns the stored OAuth tokens.
string | null
Optional explicit profile override for the binding.

POST /auth/login/device/start

Begin a device-code OAuth login.
Request
string
required
Configured credential-owner realm, explicitly supplied even when it matches the server’s active realm.
string
required
Configured binding to receive the device-flow credentials.
string | null
Optional explicit auth profile override.
Use that same credential owner/binding/profile for device completion; the example explicitly selects gemini_oauth in both requests.

POST /auth/login/device/complete

Poll one device-code OAuth completion attempt and, when ready, store the tokens under an explicit binding-scoped AuthBindingRef.
Request
Returns 202 with { "state": "pending" } when the provider has not finished, 429 with { "state": "slow_down" } when the caller should back off, and 200 with a ready payload after tokens are persisted.

GET /auth/bindings/{binding_id}/status

Read binding-scoped auth status. The response includes the flattened binding identity plus profile_id, provider, auth_method, public state, expiration, refresh timestamp, account ID, and refresh-token presence.
string
required
Binding whose status should be read.
string
required
Realm containing the binding.
string | null
Optional explicit profile override for the binding.

POST /auth/bindings/{binding_id}/logout

Clear stored credentials for the binding and publish the auth lifecycle release.
string
required
Binding to log out.
string
required
Realm containing the binding.
string | null
Optional explicit profile override for the binding.

GET /realms

List configured realm summaries.

GET /realms/{id}

Read one realm connection set.

POST /comms/send

Push a canonical comms command into a running session. Requires the comms feature.

Closed request variants

CommsSendParams is a flat, kind-tagged union, not a shared bag of optional fields. Every variant requires a string session_id identifying the sending or receiving local session and rejects unknown fields. to is a canonical peer ID, never a display name.
  • blocks is a typed ContentBlock array, not arbitrary JSON.
  • source belongs only to input: "tcp", "uds", "stdin", "webhook", or "rpc" (default "rpc"). It is not a free-text source label.
  • stream accepts "none" (default) or "reserve_interaction" on input and peer_request.
  • allow_self_session is an input-only boolean, default false.
  • content_taint is the typed SendTaintOverride on content-bearing peer variants; omission inherits the runtime’s outbound declaration.
  • handling_mode accepts "queue" or "steer". Omission/null defaults to "queue" for input, peer_message, and peer_request. It is forbidden on machine-classified progress responses. A terminal peer_response may specify it or inherit the recorded inbound request’s mode and requires that recorded request to still be repliable.
peer_message is the ordinary collaboration primitive. peer_lifecycle is a one-way topology notification: its lifecycle_kind is "mob.peer_added", "mob.peer_retired", "mob.peer_unwired", or "mob.dismiss". mob.dismiss is supervisor-directed; accepting that enum value does not grant the caller dismissal authority. Its required CommsPeerLifecycleParams object contains peer (string) and optional role, description, and typed peer_spec (BridgePeerSpec). peer_request is a structured ask with correlated replies. Its closed intent vocabulary is "supervisor.bridge" or "checksum_token", not arbitrary application labels such as "review" or "delegate". Required params must match the intent: a typed BridgeCommand for supervisor.bridge, or {"subject":"..."} for checksum_token. peer_response.in_reply_to is the request correlation ID. status accepts "accepted", "completed", or "failed". Optional result is a typed BridgeReply or a checksum result {"request_intent":"checksum_token","request_subject":"...","token":"..."}, not a free-form object. The canonical bridge shapes are meerkat_contracts::wire::supervisor_bridge::{BridgeCommand, BridgeReply, BridgePeerSpec} and their generated schema definitions.

Send receipts

Success returns HTTP 200 with a CommsSendResult selected by kind: delivery records the selected transport’s strongest proven delivery fact: acked, volatile_handed_off, queued, or a durably_resolved object with its typed ingress outcome. An acknowledgement does not prove completion of the recipient’s agent turn; a queued send does not prove receiver admission. This is distinct from the webhook routes’ 202/{"queued":true} admission response.
Unlike the two webhook ingress routes (which optionally check X-Webhook-Secret against RKAT_WEBHOOK_SECRET), /comms/send has no authentication. It is intended for trusted internal callers that already know the session ID.

GET /comms/peers

List discoverable peers for a session. Requires the comms feature.
string
required
Session ID to query peers for.
Response
peer_id is the canonical routing value accepted by POST /comms/send; name is display-only and need not be unique. address is a typed {transport, endpoint} object, while source, sendable_kinds, capabilities, and meta describe discovery provenance and supported use.

Error responses

The ordinary REST ApiError adapter returns an HTTP status and a JSON envelope:
The adapter’s data-bearing bad-request/internal variants use HTTP 400/500 with a route-supplied typed code and details. Other route-specific typed projections retain their own codes and statuses. This table is not the canonical cross-surface ErrorCode index applied wholesale: for example, create-time SessionError::Busy currently maps to BAD_REQUEST/400, not a universal SESSION_BUSY/409. Do not require this envelope on every failure. Auth routes may return only {"error":"..."}; webhook admission has route-specific error projections, and framework JSON/path extraction errors can use different bodies. For a session that was durably created but whose first turn failed, the server returns SESSION_CREATED_WITH_TURN_FAILURE with resumable session identity in details. A genuinely unknown provider failure now runs the bounded retry policy; when that policy is exhausted, details.error.kind is retry_exhausted rather than llm_failure. Clients that branch on this nested kind should accept the new value. Known non-retryable LLM failures can still use llm_failure.

Notes

Keep-alive mode requires keep_alive: true and a comms_name. If keep_alive is requested but the binary was not compiled with comms support, the server returns a BAD_REQUEST error.
POST /sessions/{id}/external-events queues a runtime-backed external event. It is a queue-only admission path, not a second direct execution loop.
hooks_override allows per-request hook overrides including adding extra hook entries and disabling specific hooks by ID. See Hooks for the HookRunOverrides schema.