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
--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.
Endpoint overview
This overview follows the generated OpenAPI artifact atartifacts/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.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>onPOST /sessionsorPOST /sessions/{id}/messages - call
POST /requests/{request_id}/cancelto cancel uncommitted in-flight work - duplicate in-flight request IDs are rejected
- 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
Server configuration details
Server configuration details
REST configuration is realm-scoped:API keys are provided via environment variables:
- macOS:
~/Library/Application Support/meerkat/realms/<realm>/config.toml - Linux:
~/.local/share/meerkat/realms/<realm>/config.toml - Windows:
%APPDATA%\\meerkat\\realms\\<realm>\\config.toml
realm_manifest.json (backend pinning) and config_state.json (generation CAS state).Key sections:ANTHROPIC_API_KEYOPENAI_API_KEYGOOGLE_API_KEY
Endpoints
POST /help
Ask Meerkat usage help with the embedded platform skill.Request
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).
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.
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 ofGET /sessions/{id}.
Query parameters:
offset— skip this many messages from the start of the transcriptlimit— cap the number of returned messages
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 suppliestarget_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
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
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 asPOST /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 suppliedX-Meerkat-Request-Id.
Response
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
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. Likeexternal-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
404 if skills are not enabled.
GET /health
Returns"ok" (HTTP 200). Use for liveness checks.
GET /runtime/host_info
Returns the read-onlyRuntimeHostInfo 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
ReturnsRuntimeHostCapabilities, a contract version plus boolean
RuntimeHostFeatureFlags. These host feature flags are distinct from the
generic status entries returned by GET /capabilities.
GET /runtime/health
ReturnsRuntimeHostHealth 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)
GET /capabilities
Returns runtime capabilities with status resolved against config.Response (abbreviated)
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:configgenerationrealm_idinstance_idbackendresolved_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> }
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 ismanaged_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.
POST /auth/login/complete
Complete a loopback OAuth login and store the resulting tokens under an explicit binding-scopedAuthBindingRef.
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.
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-scopedAuthBindingRef.
Request
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 plusprofile_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 thecomms 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.
blocksis a typedContentBlockarray, not arbitrary JSON.sourcebelongs only toinput:"tcp","uds","stdin","webhook", or"rpc"(default"rpc"). It is not a free-text source label.streamaccepts"none"(default) or"reserve_interaction"oninputandpeer_request.allow_self_sessionis an input-only boolean, defaultfalse.content_taintis the typedSendTaintOverrideon content-bearing peer variants; omission inherits the runtime’s outbound declaration.handling_modeaccepts"queue"or"steer". Omission/nulldefaults to"queue"forinput,peer_message, andpeer_request. It is forbidden on machine-classified progress responses. A terminalpeer_responsemay 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 HTTP200 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 thecomms 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 RESTApiError 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.