Skip to main content
Mirrored from Meerkat Meta main at 26f5ae9da1fd on 2026-11-03; Meta version 0.9.9 at that commit. Meta serves a REST API over HTTP using Axum. The API provides console access, runtime inspection, timeline replay, JSON-RPC ingress, legacy console send compatibility, multipart upload/send, and blob retrieval.

Base URL

The console/admin meta_gateway and SDK-facing persistent rpc_gateway bind an available loopback port and report the exact http_base_url in their meta/init result. Use that value for every route on this page; do not assume port 8080. A Rust library host chooses its own listener and must supply that base URL to its clients.

Console endpoints

GET /console

Returns the admin console HTML page. The page is a self-contained single-page React application with no external dependencies. Response: text/html

GET /console/assets/console-app.js

Returns the console React bundle as JavaScript. Response: application/javascript

GET /console/assets/console-app.css

Returns the console stylesheet. Response: text/css

JSON API endpoints

GET /console/experience

Returns experience metadata describing the current runtime state. This is the primary data source for the console UI. Response:
When a Meerkat mob runtime is configured, the response includes live agent data from the mob. Without a mob runtime, only module-level information is returned. When console UI config is loaded, the response includes console_config, the view-level contract consumed by the bundled console. An agent row in agent_sidebar.live_snapshot.agents, and its rows in identity_status and health_overview, can carry last_activity_ms: the newest time, in milliseconds since the epoch, that the console’s frame log recorded the agent acting (a live runtime event, a console send or a tool application). Frames rebuilt from session history or synthesized by the console do not count, and neither do lifecycle records (respawn, retire, reset, delete), interaction failures and memory events, which record something done to the agent. The field is advisory: a row without it has no known activity, and reading it wakes no agent. A row answered pending (below) keeps it, since the frame log answers it, not the read model. The gateway reads each agent’s newest acting frame from the log on the first experience after it starts that lists the agent, 256 agents per store call, and keeps the time in memory after that. At 10,000 agents that first read takes about 56 ms in all when the agents’ newest frames record them acting, and longer when many of those frames do not (rebuilt history, for example), at about 0.4 to 0.9 microseconds per such frame. A retire, a delete or a clear made meanwhile waits for at most one such call, and so do the console writes queued behind it. topology.live_snapshot.mob_id is the id of the mob whose members the topology shows, when one mob is known: a runtime’s console reports its own mob, and a shared console the mob of its one registered runtime. It is absent on a shared console over several runtimes or none, and from a snapshot a host builds without it. The console names the topology’s team from it, never from runtime_id. health_overview.live_snapshot.running is the mob owner’s answer when the experience is read. A console served from the console aggregator answers for its registered runtimes: true only while every registered runtime’s mob is creating or running, and false when at least one is not. Without a runtime that can answer (no live mob runtime, or an aggregator with no registered runtime), it is null, and the snapshot names it in an unobserved marker: {"reason": "not_observed", "fields": ["running"]}. An identity-first member’s records take its session from the identity runtime, never from a walk made under an earlier identity binding. When the member’s binding has moved (after a reset, retire or delete), a read collects the member again first and waits at most 2 s. Past that bound, the member’s sidebar row answers with the identity runtime’s session, the member’s run progress and state unknown, and offers no send or retire. Each record of the member (the sidebar row, the identity_status and health_overview rows, and the topology node) withholds the other facts the earlier walk found and names them in an unobserved marker with reason refresh_pending, for example {"reason": "refresh_pending", "fields": ["label", "role", "state", "wired_to", "addressable"]} on the topology node. Access and visibility still decide on the last observed facts, so a label-scoped deny keeps such a member hidden.

GET /console/modules

Returns the configured module IDs from RuntimeDecisionState. This response does not contain per-module health or startup state. Response:

GET /console/identities

Returns visibility-filtered identities from the console aggregator, including ordinary runtime/worker identities; an identity-first runtime is not required. Without a console aggregator, this route returns 404 with error: "unavailable". Response: {identities: ConsoleIdentityRecord[]}. Each record carries identity, display_name, runtime_key, runtime_member_id, optional session_id, optional member_id (Core’s mob member id of the identity’s last binding, live or retired: the identity’s roster member id, as Core’s member tokens name it, which is Meta’s comms-safe encoding of the identity and starts with mk-- when the identity has a :; absent when Core holds no binding of the identity and on records of raw runtime members), visibility, addressable, health, labels, and topology_peers when non-empty. Use meta/console/inspect_identity for identity/peer inspection and /console/experience for affordances; neither identities nor inspection records contain an affordances field.

GET /console/timeline

Returns console timeline frames from the Meta console log store. This endpoint is the REST equivalent of meta/console/query_timeline.
Response frames are visibility-filtered and returned in display order. next_cursor is safe to use as the next after cursor for since pagination; latest_cursor is the latest cursor for the target window and is useful for live continuation after a recent seed. The log keeps a streamed block’s text_delta and reasoning_delta frames until the block’s text_complete or reasoning_complete frame is stored, then removes all of them but the first, which keeps the block’s place in the timeline: a later read returns that first frame and the complete frame, whose text supersedes it, and live streams have already delivered each delta. A block that never completes keeps its deltas. Cursors are never renumbered, so the cursor of a removed frame still works as after or before.

GET /console/timeline/stream

Streams timeline replay followed by live console frames (text/event-stream). It accepts the same query parameters as GET /console/timeline, but limit does not bound the whole replay: recent produces one bounded seed page (limit sizes it), while anchored since replays all retained matching backlog, sent page by page as it is read, whatever limit says. Without after, since switches to recent. A non-empty Last-Event-ID header overrides the URL’s after parameter. See the timeline SSE contract for event envelopes and snapshot markers. If the requested cursor can no longer be replayed, the route returns HTTP 409 with {"error":"replay_unavailable"} and cursor context. Clients should refetch a recent page and resume from latest_cursor. An anchored replay can also end after the stream opened: if the log no longer holds what follows the frames sent (it was cleared during the replay), the stream sends a replay_unavailable event and ends, to be handled as the 409; if a later page cannot be read, the stream ends after the frames it sent, with no snapshot_complete, and the client reconnects with the last frame’s cursor as Last-Event-ID.

GET /console/identity/{identity}/stream

Identity-filtered convenience route for the same console timeline stream. It sets the identity filter from the path and accepts the remaining timeline query parameters.

POST /console/send

Legacy console send route backed by the console aggregator. New clients should prefer POST /console/rpc with meta/console/send.

POST /console/send/recover

REST twin of meta/console/recover_send: the body is {identity, origin, idempotency_key} and the caller is the request’s authenticated principal. It runs the same gate as /console/send (read-only and agent.send refusals). A send the caller does not own, or never made, answers HTTP 404 with {"error":"send_not_found"}. A candidate launch (runtime_options.mob_composition.authority = "candidate") settles no send and answers HTTP 409 with {"error":"candidate_launch"}, as /console/send does: recover the send on the authoritative launch. It waits for the runtime’s admission evidence as the RPC does: up to 45 seconds in all when the store is slow (after a restart, reading the evidence loads the session the send recorded), so an HTTP client whose timeout is shorter gives up first. console_fetch_timeout_ms, the bundled console’s fetch timeout, should stay above 45 seconds.

POST /console/rpc

JSON-RPC 2.0 endpoint for console and runtime operations.

POST /console/rpc/multipart

Multipart JSON-RPC endpoint. Supported methods: For meta/blob/upload, send a payload part containing this JSON-RPC request, plus exactly one binary part named file:upload-1 with Content-Type: image/png:
The upload fields may instead be directly in params. part_name is an alias for upload_id; both identify the suffix after file:, with upload_id taking precedence if both are present. type is optional and, when supplied as a string, must be image_upload. Optional media_type must match the file part’s MIME type. Supported types are image/png, image/jpeg, image/webp, and image/gif, with a 25 MiB per-file limit. The result contains blob_id, media_type, and size in bytes, not url or content_type. Fetch the bytes through GET /blobs/{blob_id}. Console sends with image placeholders remain subject to the multipart image-count and per-file byte limits.

GET /blobs/{blob_id}

Returns blob/image content by ID when a blob store is configured.

Authentication

The reference router keeps its frontend shell and bootstrap assets public:
  • /, /favicon.ico, and /healthz
  • /console, /console/, and /console/assets/*
  • /flow-editor, /flow-editor/, and /flow-editor/assets/*
When ConsolePolicy.require_app_auth is true, the console JSON, JSON-RPC, timeline, blob, Flow Editor RPC, and SSE surfaces require a valid JWT. Clients may use either a bearer header:
or an exact query parameter:
Query values are percent-decoded. URL-encode reserved characters such as =, &, +, and %. Prefer the header except for browser or SSE bootstrap cases. The token is validated against the fixed trusted OIDC snapshot and configured auth policy. See authentication for details. Meta’s built-in console auth is bearer-token based and does not rely on cookies. Deployments that add cookie-backed console auth at a reverse proxy must also add CSRF/origin protection for mutating console routes. Set ConsolePolicy.read_only to true to serve the console in view-only mode. Read endpoints and streams remain available, while mutating JSON-RPC methods such as meta/console/send, meta/blob/upload, lifecycle controls, gating decisions, and metadata writes are omitted from meta/capabilities and return a read-only error if called directly.

Error responses

API errors return standard HTTP status codes with a JSON body:
Structured /console/send admission failures carry {error, message, data}: 429 uses admission_backlog_full, 503 uses actor_probe_unhealthy or reload_refused, and 504 uses admission_timeout or reload_timed_out. Preserve data when reporting these failures; a timeout does not by itself establish execution fate or retryability. These REST status mappings are distinct from the JSON-RPC policy codes documented in the RPC reference.

See also