Skip to main content

Base URL

For self-hosted deployments, the base URL is http://localhost:8080. JSON endpoints return application/json. Errors use:

Tenancy

Authenticate with your tenant API key using Authorization: Bearer ao_.... The tenant is derived from the credential; client-supplied X-Tenant-ID headers are ignored. Access is limited by your role. See Roles & Access.

Who Am I

Returns the caller’s resolved identity: tenantId, role, and authKind (api_key, run_token, or session).

Caller Preferences


Profiles

List Profiles

Returns a page of profiles registered on the connected runner or runner pool. limit defaults to 200 when omitted; the response always includes X-Total-Count. Pass limit/offset to page explicitly.

Get Profile

Returns one stored profile by name. Unknown names return 404 Not Found.
Response 200 OK: the stored profile.

Create Profile

Creates a profile and broadcasts it to all runners. id is assigned when omitted. Profile list and get responses may include workerPlacement for sandbox-worker profiles when the Conductor can resolve the live runner fleet. It reports the resolved substrate, source, and servable state, with requested and detail when the requested placement cannot be honoured or the answer is ambiguous. The field is computed on read, ignored on create or update, and never persisted. See Worker Placement.
Response 201 Created: the stored profile.

Update Profile

Replaces an existing profile and broadcasts the new definition to all runners. The request body uses the same fields as create. Like create, PUT /api/profiles/{name} does not validate skill names against any skill store: an unknown or not-yet-installed skill name is accepted, and readiness reporting (not a 400) is how the caller learns a skill dependency is missing. Response 200 OK: the stored profile.

Delete Profile

Deletes a profile from every runner. Existing sessions keep their snapshot.

Pin Profile

Unpin Profile

List Profile Changes

Returns the profile’s change history.

Get Profile Usage

Returns usage totals for one profile.

Attach Skill

Attaches a skill to a profile by name.

Detach Skill

Removes a skill from a profile.

Seed Orca Profile

Idempotently (re-)creates the canonical sonar helper profile. Used by the dashboard’s Quickstart “Restore Orca” action; the route keeps the orca name because it is the platform’s. Returns { "profileName": "sonar", "created": <bool> }.

Publishing Profiles

Publishing turns a conductor profile into a public chat-gateway route. The publish endpoints require Postgres. Key issuance also requires AGENT_API_KEY_PEPPER; plaintext API keys are returned exactly once and are stored only as HMAC hashes. Publishing has no billing or credit check. Only run creation and plan autostart/execution are gated by the credit gate; see Create Run and Billing.

List Published Agents

Returns all active published agents in the current tenant:
publicUrl is present only when CHAT_GATEWAY_PUBLIC_HOST is set.

Get Published Agent

Returns the active published row for a profile, or 404 not_published.

Get Published Agent Metrics

Returns request, run, conversation, cost, and token metrics for the active published agent behind a profile. The response is scoped to one published agent; profiles that are not published return 404 not_published. Query params:
buckets uses the same shape as GET /api/stats/timeseries so dashboard charts can render the scoped view directly. sandboxSeconds and failedRuns are always 0 in this per-published-agent response because those values are not attributable to one published agent from usage_records.

Publish Profile

All fields are optional; omitted values default server-side.

Update Published Agent

Partially updates the active published row. slug is immutable; unpublish and publish again to change it. The patch also accepts enabled and monthlyCostCapUsdCents (integer, must be >= 0; monthly spend cap for this published agent in USD cents).

Unpublish Profile

Soft-deletes the active published row and returns 204 No Content.

List Agent Keys

Returns active and revoked key metadata for the active published row. The key hash and plaintext token are never returned.

Issue Agent Key

Returns 201 Created with key metadata plus one-time plaintext:

Revoke Agent Key

Revokes one key and returns 204 No Content.

Tenant API Keys

Tenant API keys are durable control-plane bearer credentials for SDKs, CI, and scripts calling /api/*. They are separate from published-agent keys under /api/profiles/{name}/keys: published-agent keys authenticate public chat-gateway traffic, while tenant API keys authenticate the conductor API directly. These routes require Postgres. Key issuance also requires AGENT_API_KEY_PEPPER; plaintext tokens are returned exactly once and are stored only as HMAC hashes. A key inherits the minter’s RBAC role, and route-level RBAC still applies.

List Tenant API Keys

Returns key metadata for the current tenant. Admins and owners see every key in the tenant; members see only keys they created. Tokens and hashes are never returned.

Issue Tenant API Key

Returns 201 Created with key metadata plus the one-time plaintext token:
Send the token as Authorization: Bearer <token> on future conductor API requests. The conductor only verifies these keys when it has Postgres and a decodable AGENT_API_KEY_PEPPER.

Revoke Tenant API Key

Revokes one key and returns 204 No Content. Members can revoke only their own keys; admins and owners can revoke any key in the tenant.

Tenants

Operator-only control-plane CRUD for tenant records, restricted to the single tenant named by the conductor’s AGENT_ORC_ADMIN_TENANT operator configuration; every other caller, including an admin of any other tenant, gets 404 Not Found. Returns 503 Service Unavailable when no database is wired. Tenant IDs are slugs matching the X-Tenant-ID middleware format.

Pods

List Pods

Returns a page of named agent pods. limit defaults to 200 when omitted; the response always includes X-Total-Count. Pass limit/offset to page explicitly.

Get Pod

Returns one stored pod by name.

Create Pod

Creates a pod and broadcasts it to all runners.
Response 201 Created: the stored pod with an assigned id.

Update Pod

Updates supplied fields on an existing pod and broadcasts the new definition to all runners.

Pin Pod

Unpin Pod

Add Pod Member

Adds a profile to a pod.

Remove Pod Member

Removes a profile from a pod.

Delete Pod


Sessions

List Sessions

Returns a paginated page of session records visible to the conductor. limit defaults to 200 when omitted; the response always includes X-Total-Count. Supports q (substring match on session ID or profile), profile (exact profile-name filter), and group (filter by rail group, applied after pagination).
Session statuses are idle, running, errored, and shutdown.

Create Session

Mints a runtime session bound to the named profile, used by the chat gateway’s first-turn path so a session id exists before dispatching the run.
Response 201 Created:

Get Session

Returns one session DTO with the same shape as list entries.

List VFS Leases

Returns active per-session VirtualFS leases visible to the conductor. In pooled deployments the conductor fans out to runners and merges the results; if no VFS manager is wired the response is [].
An empty allowedMounts array means the VirtualFS session is unrestricted.

Get Session VFS Lease

Returns the VirtualFS lease for one session, or 404 with { "error": "no vfs session" } when no lease exists.

Delete Session

Terminates a session on its owning runner.

List Session Runs

Returns run summaries whose subTask.sessionId matches the session.

Get Session Rail

Returns the session rail’s grouped view in one call, so the dashboard rail renders from one request instead of several filtered session queries.

Runs

Create Run

Submits a SubTask. The conductor creates a session when sessionId is omitted and returns immediately. Unknown profiles return 400 with an unknown_profile: <name> error. Client-supplied id and sessionId values must be 1-128 characters and contain only letters, numbers, underscores, or hyphens. Invalid values return 400 with invalid run id or invalid session id. Leave either field empty or omit it when you want the conductor to mint a safe identifier. When sessionId is supplied in a Postgres-backed deployment, the conductor first reuses the live runner session if it is still in memory. If the runner registry no longer knows the session after a conductor, runner, or sidecar restart, the conductor hydrates it from the durable session row and any saved state bundle before dispatching the run. A missing or shut-down durable session returns 404 instead of creating a new session under the caller-supplied id. Hydration infrastructure failures, such as a database read failure or runner transport error, return 503 Service Unavailable so clients can retry instead of treating the session as gone. When the billing client is wired, run creation is blocked before session or sandbox allocation if the tenant’s wallet balance is exhausted or the credit check itself fails. The response is 402 Payment Required with { "error": "credits_exhausted" | "credit_check_unavailable", "scope": "tenant", "balance_micro_usd": <number>, "message": "..." }.
Response 202 Accepted:
Response 409 Conflict: returned when the profile’s dependencies are not ready, such as a connected app, secret, or catalog:// skill reference that is missing or not yet installed:

List Runs

Returns newest-first run summaries.
Run statuses are running, ok, error, cancelled, and interrupted. interrupted marks runs orphaned by a conductor restart and closed by the boot reconciliation sweep.

List Profile Runs

Returns run summaries whose subTask.profile matches the profile.

Get Run

Returns a run summary plus the buffered event log.

Cancel Run

Cooperatively cancels a running run and returns 204 No Content. The operation is idempotent: already-finished runs remain in their terminal status. An unknown run in the current conductor’s in-memory registry returns 404 Not Found.

Force-Terminate Run

Force-stops a run when cooperative cancellation does not complete. The conductor independently attempts to cancel its local run context, wait up to two seconds for the session’s run claim to drain, and change a still-running durable row to cancelled. If the claim does not drain, the conductor force-releases it. Once the durable row is cancelled, a late run completion cannot overwrite that status. The endpoint is idempotent and returns 200 OK even when the run is absent from the current conductor’s in-memory registry, allowing it to recover runs stranded by a restart. Inspect every result field and warnings rather than treating the status code alone as proof that all steps completed.

Stream Run Events

Opens a Server-Sent Events stream. Subscribers receive replayed buffered events first, then live events.
Event types are progress, session_init, assistant, tool_call, tool_result, usage, result, error, thinking, capability_span, capability_decision, and capability_context_savings. The runtime session id is not on this SSE surface; read it from the raw GET /api/runs/{id}/events JSONL stream, which carries the underlying SDK/thread id per event, or from the session row’s metadata.

Billing

Billing routes are not uniformly admin-gated: GET /api/billing/wallet requires member role or higher, while POST /api/billing/checkout requires owner role. In dev, when the conductor is not wired to the billing service, wallet and checkout calls return 503 Service Unavailable. When AGENT_ORC_ENV is any non-dev value, the conductor instead refuses to start without a valid billing client so runs cannot silently bypass the credit gate.

Get Billing Wallet

Returns the tenant’s live credit balance from Polar plus the purchasable top-up packs.

Create Billing Checkout

Creates a Polar hosted checkout URL for a one-time prepaid credit pack. The cents body field must match one of the packs returned by GET /api/billing/wallet.
Response 200 OK:

Get Spend Cap

Reads the tenant’s monthly spend cap.

Update Spend Cap

Sets the tenant’s monthly spend cap. Requires admin role or higher. The enforcing gate lives in run creation.

Billing Webhooks

POST /api/clerk/webhook and POST /api/polar/webhook are signature-verified webhook targets (Svix and Standard Webhooks HMAC respectively, checked inside the handler), not endpoints an SDK or CLI calls directly.

Notifications

Send Notification Email

Sends a notification email, used by the @notify email_me tool.

Get Notify Settings

Update Notify Settings

Requires admin role or higher.

Workflow Runs

Workflow run snapshots currently encode status and node status as integer enums. Create/start responses expose string status labels.
Workflow run status mapping: 0=pending, 1=running, 2=paused, 3=completed, 4=failed, 5=cancelled. Node status mapping: 0=pending, 1=ready, 2=running, 3=ok, 4=error, 5=skipped, 6=cancelled.

Create Workflow Run

Creates a DAG workflow run. autoStart defaults to true.
Response 202 Accepted:

List Workflow Runs

Optional filters:

Get Workflow Run

Returns the full workflow run snapshot.

Start Workflow Run

Submits a pending workflow run to the engine. Idempotent for non-pending runs.

Cancel Workflow Run

Cancels pending and running nodes where possible.

Repair Workflow Run

Applies a repair action to a paused workflow run.
Supported action types are retry_node, replace_node, add_dependency, and abort.

Update Node Status

Reports one node’s outcome and drives orchestrator-controlled transitions. status must be one of ok, error, skipped, or cancelled.
Returns 202 Accepted with the updated workflow run snapshot. Unknown run or node returns 404 Not Found; an invalid status or a node not in a pending state returns 400 Bad Request.

Stream Workflow Run

SSE stream with a snapshot event followed by plan_status events. Each frame’s payload is keyed by workflowRun:

Create Workflow Definition

Creates a reusable workflow graph template. name and a non-empty nodes array are required.

List Workflow Definitions

Returns a page of the tenant’s workflow definitions. limit/offset query params page the list, defaulting to and capped at 200; the total count is returned in X-Total-Count.

Get Workflow Definition

Returns one workflow definition.

Update Workflow Definition

Updates supplied fields on an existing workflow definition.

Delete Workflow Definition

Deletes one workflow definition. Returns 204 No Content on success.

Create Workflow Schedule

Creates an active schedule for an existing workflow definition. workflowDefinitionId and cron are required.

List Workflow Schedules

Returns a page of the tenant’s workflow schedules. limit/offset query params page the list, defaulting to and capped at 200; the total count is returned in X-Total-Count.

Get Workflow Schedule

Returns one workflow schedule.

Pause Workflow Schedule

Marks a workflow schedule as paused.

Resume Workflow Schedule

Marks a workflow schedule as active.

Delete Workflow Schedule

Deletes one workflow schedule. Returns 204 No Content on success.

Automations

Agents and pods on a schedule. Requires Postgres; every route returns 503 Service Unavailable when POSTGRES_DSN is not configured. See Automations for the domain model, lifecycle, and how a firing relates to an ordinary run.
/api/schedules/* is a product-facing alias over the same handlers as /api/automations/*, not a second implementation. GET, POST, GET /{id}, PUT /{id}, DELETE /{id}, POST /{id}/run, POST /{id}/start, and POST /{id}/stop share handlers under both prefixes. POST /api/schedules/parse exists only under /api/schedules; GET /api/automations/firings, POST /api/automations/{id}/pause (a deprecated alias for stop), and GET/PUT /api/automations/{id}/memory exist only under /api/automations.

List Automations

Returns a page of automations. limit defaults to 200 when omitted; the response always includes X-Total-Count. Query params: q (free-text filter), includeDeleted=true (also page over soft-deleted rows).

Create Automation

Created active, on the schedule it was given. Returns 201 Created with the stored automation, or 400 Bad Request when the cron expression, timezone, or window is invalid, or would never fire.

Get Automation

Returns one automation, including soft-deleted rows.

Update Automation

Same body as create, minus name (an automation cannot be renamed). Editing an active automation recomputes its next run; a change that would leave the window already closed stops the automation instead. Returns 409 Conflict when the automation is disabled.

Delete Automation

Soft-deletes the automation once its worker, delegated children, and automation-agent update have all finished. Firings and sessions remain as read-only history.

Start Automation

Sets the automation to active and computes its next run from now (no catch-up for time spent stopped). Also clears its consecutive-failure count. Returns 409 Conflict when the automation’s end date has already passed.

Stop Automation

Sets the automation to stopped and clears nextRunAt. Starting again computes a fresh next run rather than resuming a suspended countdown.
POST /api/automations/{id}/pause is a deprecated alias for this same handler.

Run Automation Now

Fires one slot immediately, through the same code path a scheduled firing takes. Works on a stopped automation without starting it, so credentials and target can be tested before switching the schedule on. Does not touch nextRunAt. Returns 202 Accepted with the firing.

List Automation Firings

Query params: automation (id, scopes to one automation’s history), outcome (ok, failed, or skipped), limit, offset. Serves both the global history view and a single automation’s runs tab.

Get Automation Memory

Returns the short note the automation’s own automation-agent keeps between firings:
An automation whose agent has not written yet returns an empty body with no updatedAt.

Set Automation Memory

Body: { "body": "..." }, capped at 8KB. An empty body clears the note. Overwrites what the automation agent would otherwise keep unsupervised.

Parse Schedule Text

Turns a plain-language cadence into a cron expression, for the dashboard’s schedule popover.
Returns 422 Unprocessable Entity when the text cannot be parsed as a cadence.

Skills Catalog

Skills are named instruction bodies that profiles can attach by name. Responses include source, which is user, imported, or platform, plus Agent Skills metadata such as license, compatibility, allowedTools, metadata, resources, and requiresSandbox. Platform skills are seeded using-* guidance entries tied to capability bundles and may be auto-attached from profile tools. Folder import is a staged flow. First upload exactly one skill folder containing SKILL.md; each multipart files part must use the package-relative filename, such as my-skill/SKILL.md or my-skill/scripts/run.sh. The dry-run response returns validation details, resource metadata (contentType, sha256, executable), requiresSandbox, totalBytes, and a short-lived stagingId. Commit that stagingId to register the package. Deleting a platform-seeded skill records a tombstone so later seed reconciliation does not restore that deleted using-* skill. Filesystem deployments store tombstones in platform_skills_state.json; Postgres deployments store them per tenant and reconcile platform skills lazily on the tenant’s first skills-list or run-dispatch request.

Templates and Kits

A template is a snapshot of a pod, a single agent, or an automation (with whatever it runs) into a copyable record: skills, sanitized profiles, and optionally a pod and its schedules. A kit is the same record addressed by a public share id (kit-...) instead of a slug, which is how it stays reachable after a rename. See Shared kits for the dashboard’s browse and install flow, and Prompts for the profile side of what the record carries. The API routes keep the older templates spelling; the product calls the thing a kit. Env values, literal credential-shaped MCP header values, and computed or row-identity fields are stripped at snapshot time regardless of what the manifest shows, so a template payload never carries a secret.
GET /api/public/templates/{slug}, GET /api/public/kits/{id}, and POST /api/public/kits/{id}/events are public: no session or API key is required. They serve the sanitized share-landing read (and its view/click log) that a visitor with no workspace follows from a shared link. GET /api/templates/{slug}, GET /api/kits/{id}, and POST /api/kits/{id}/events are the same reads and the same event log for a signed-in caller, who is then stamped with their own workspace. Everything else on this page requires at least member role; POST, PATCH, and DELETE /api/templates (create, edit metadata, delete) and both republish routes require admin role or higher.

Preview Template

Dry-run manifest of what a snapshot would contain, before creating it. Names exactly one of pool, agent, or automation; schedules names automations to attach (valid only alongside pool or agent).
Returns the manifest: profiles, skills, pod files, and any attached automations, plus a stripped list documenting what the sanitizer removed. Automations installed by a kit always arrive stopped, with no prompts attached; the manifest says so in each automation’s detail line.

List Templates

Lists the caller’s own templates by default. ?scope=saved lists templates bookmarked from other workspaces’ share links; ?scope=copied lists templates this workspace has installed. Each row carries authorHandle and authorName (the author’s public identity, never their tenant id) and, on the own-templates list, view/copy/save counts when available.

Create Template

Requires admin role or higher: publishing a template shares its content with anyone who has the link.
Returns 201 Created with the stored template (a fresh id and public kit-... id), or 409 Conflict when the slug is already taken in your workspace.

Get Template

Readable by any authenticated workspace, which is what makes the link shareable. Accepts ?org={handle} to disambiguate a slug two organizations both publish, and ?id={id} to read an exact saved snapshot. Returns 409 Conflict when the slug is ambiguous without ?org=, and 410 Gone for a template its owner deleted (unless the caller has it saved).

Update Template

Requires admin role or higher. Author-only edit of label and description; payload changes go through republish so they always pass the snapshot sanitizer.

Delete Template

Requires admin role or higher, author-only. Returns 204 No Content. A platform-seeded built-in kit cannot be deleted.

Get Template Copy Plan

Computes what copying now would do, asset by asset: new, changed (differs from what you installed before), or unchanged, plus a suggested target name when the caller already has something with that name.

Copy Template

Instantiates the snapshot into the caller’s tenant: skills first, then profiles, then the pod, then any included workspace files. Idempotent on the pod or agent name: an existing same-named asset means “already copied” and is never overwritten. With no body, copies everything. With a body naming assets (from the copy plan), copies and renames only the selected assets:
Returns 200 OK with { "copied": false, "reason": "already_exists", ... } when nothing new was installed, or 200 OK with { "copied": true, ... } naming what was added. Any automation the kit carries installs stopped. Returns 424 Failed Dependency if a skill fails to install.

Republish Template

Requires admin role or higher. Re-snapshots one of the caller’s own live sources through the same sanitizer and size checks as create, and advances the template’s version. Same body as create.

Republish Saved Template

Requires admin role or higher. Forks the exact frozen snapshot a caller saved into a new template and link they own, under a new slug and label (query param id selects which saved snapshot). Returns 403 Forbidden for a template the caller has not saved, or one they authored themselves. Returns 409 Conflict when the caller’s workspace has no public org handle.

Save Template

Bookmarks someone else’s template without copying it. Returns 400 Bad Request for the caller’s own template.

Unsave Template

Removes the bookmark. Returns 204 No Content, or 404 Not Found if it was not saved.

Get Kit

The same read as Get Template, addressed by public kit id (kit-..., or a built-in kit’s name) instead of slug. Mine, save state, and counts follow the same rules.

Record Kit Event

Logs a view of, or a click on, a kit’s page, for the author’s own counts. Best effort: returns 204 No Content even when the event log itself is unavailable, so a page view never fails on this call.

Capabilities

A bounded capability facade for tenants using the capability router. discover and run require an active run-token context (they operate on behalf of the run that calls them).

Capability Bundles

Lists capability bundles available to attach to a profile.

MCP Server Catalog

List Catalog Entries

Returns a page of catalog entries. limit/offset query params page the list, defaulting to and capped at 200; the total count is returned in X-Total-Count.

Get Catalog Entry

Create Catalog Entry

Update Catalog Entry

managedBy is server-owned. Both create and update ignore a client-supplied value; provider-managed entries are created only through the Connected Apps flow.

Delete Catalog Entry

Test Catalog Entry

Body is an MCP server spec, the same shape as create/update. The server validates it and performs a dry-run connection attempt (5 second timeout) using the tenant’s resolved secrets, returning the connection test result.

Connected Apps

These tenant-scoped endpoints manage provider-managed app connections and, once connected, proxy tool discovery and execution through the internal MCP bridge so provider credentials do not enter profiles or worker run envelopes.

Connection Management

The tool discovery and execution endpoints below are available only for provider-managed toolkits that are active in the MCP catalog.

List Connected App Tools

Returns tool names, descriptions, and input schemas. Returns 403 Forbidden when the toolkit is not active for the tenant.

Call Connected App Tool

name is required. The response is the provider MCP tool result; bridge failures return 502 Bad Gateway.

Topology

Get Runner Topology

Available whenever the conductor has a runner pool, including a pool of one runner. Returns an array in registry order. In addition to point-in-time health and session data, each entry reports membership state:
  • live: eligible for new sessions
  • draining: existing sessions remain routable, but new sessions avoid the runner
  • unreachable: the last runner probe failed; new sessions avoid the runner
capabilitiesKnown is false until the runner has reported its capabilities; such a runner is not eligible for new sessions. A successful probe with an empty capabilities list retains the backward-compatible meaning “supports all runtimes.” Fields such as process, sessionsByRuntime, and sidecars are null when the runner cannot provide that detail. This tenant-facing response omits runner base URLs, sidecar endpoints, PIDs, and Node versions. Sidecar instanceId values contain only the distinguishing suffix of the underlying process identity; host prefixes are removed. Probe and run errors also redact the associated infrastructure address. The full shape remains available to operator tooling on the internal topology plane. The nested details are intentionally observation-based:
  • process is the runner’s Go-process footprint (allocBytes, sysBytes, heapInuseBytes, numGoroutine, numCPU, and uptimeSeconds).
  • sessionsByRuntime breaks down live sessions on that runner across runtimes.
  • sidecars lists runtime slots. A slot can be an in-process stub or a sidecar with state live, cold, degraded, or unreachable; cold means configured but quiet and is not a fault.
  • Each sidecar’s observedInstances is a lower bound, not a replica count. Connection pooling can hide idle replicas, and slots sharing a sidecar process can report the same process.
  • Instance and process statistics can be null until a health probe reaches that process. Null means not measured and must not be rendered as zero.
The topology fan-out is cached for 30 seconds and shared across concurrent callers. When per-tenant rate limiting is enabled, GET /api/topology also has its own tier, configurable with AGENT_ORC_RATELIMIT_TOPOLOGY_RPM and defaulting to 30 requests per minute. The dashboard’s Runtime page does not use this endpoint. It shows the workspace’s own sandbox worker leases, polled every 10 seconds. GET /api/topology remains available as a published SDK surface.

Stats

All stats endpoints accept window, a Go duration string such as 5m, 1h, 24h, or 168h. The window is capped at 30 days. Agent sort values: last_activity_desc, tokens_desc, failures_desc, runs_desc, sessions_desc, name_asc.

Stats Timeseries

Returns buckets on one shared time grid. Each bucket includes run counts, token usage, costCents, sandboxSeconds, and ingressRequests. costCents, sandboxSeconds, and ingressRequests are zero when the usage store is not configured. Sandbox seconds are attributed by the sandbox meter row’s recorded_at timestamp, so a bucket contains sessions whose sandbox meter last refreshed inside that bucket rather than exact in-bucket compute.

Usage Meters

Returns the tool-call, sandbox-compute, and published-agent ingress meter totals recorded in usage_records. window uses the same values and 30-day cap as the stats endpoints. totals is always the tenant-wide rollup for the window. Query params:

Admin Pricing

Requires admin role. Returns the compiled-in price floor plus live reconciler rates.

Storage

Storage is backed by the optional S3-compatible artifact client configured from AWS_* / S3_* environment variables.

Storage Info

Always returns 200 OK. When storage is not configured, configured is false.

List Objects

Query params:

Get Object

Returns inline object content. key may contain multiple path segments. Non-UTF-8 content is base64 encoded.

Upload Object

Stores the raw request body at key. The Content-Type header is preserved. Inline payloads are capped at 8 MiB; oversize requests return 413 Payload Too Large.

Delete Object

Removes one object. When key ends with /, the path is treated as a folder prefix and every object under it is deleted (capped at 1000 entries, one List page).

Secrets

Secrets are tenant-scoped and envelope-encrypted. These routes return 503 Service Unavailable unless both Postgres and AGENT_ORC_MASTER_KEY are configured. Plaintext is required on writes and is never returned by metadata endpoints.

List Secrets

Returns metadata only:

Create Secret

key is optional and names the canonical environment variable or credential slot the value is meant to populate.

Update Secret

Uses the same body as create. If name is present in the body, it must match the path.

Delete Secret

Deletes the secret metadata and ciphertext.

Context Documents

Context documents are human-authored markdown a run can be seeded with. Do not store secrets here; use Secrets instead.

VirtualFS Proxy

When VFS_BASE_URL is set on the conductor, it mounts a reverse proxy at /api/vfs/ for the standalone VirtualFS server. The proxy strips the /api prefix before forwarding, so GET /api/vfs/mounts reaches upstream GET /vfs/mounts. GET /api/vfs/leases is the conductor’s runtime lease endpoint documented in Sessions, not a proxied standalone VirtualFS route. Upstream connection failures return 502 Bad Gateway with vfs upstream unavailable.

Memory Bank

Per-profile long-lived memory plus a global admin view. See Memory Bank for the data model, ranking, and prompt injection. All endpoints return 503 Service Unavailable when the bank is not wired.

List Profile Memories

Query: limit (default 100), offset (default 0).

Create Profile Memory

Body fields (MemoryCreateRequest): Either rawInput or processedContent is required. Returns 201 Created with the stored memory. Unknown profile returns 404.

Search Profile Memories

Query: q (search string, empty matches all), limit (default 8), minScore (default 0.05).

Get Profile Memory

Delete Profile Memory

Get Memory Bank

Global admin view used by the dashboard’s Memory Bank page. Without limit, returns the legacy grouped response. With limit, returns a flattened page using limit and offset; q searches memory content, summary, profile name, or ID, and category filters by memory category. Paged responses include total, profilesWithMemory, limit, offset, and items.

Get Memory Bank Stats


Contact

Submit Contact Enquiry

Public, unauthenticated contact-form submission endpoint. Sends the enquiry and a branded confirmation email to the submitter.

Device Authorization

The RFC 8628 device-authorization flow behind orca login and other headless CLI or agent sign-ins. See Headless authentication and Authentication and contexts for the end-to-end flow; this section documents only the wire format. All routes return 503 Service Unavailable when the flow is not configured (Postgres, the tenant key pepper, and AGENT_ORC_DASHBOARD_URL are all required).
POST /api/device/code and POST /api/device/token are unauthenticated: the caller has no credential yet, which is the point. GET /api/device/authorization, POST /api/device/approve, and POST /api/device/deny require a signed-in session (member role or higher) in the browser tab that is completing the approval.

Request Device Code

client_label is optional, free text up to 100 characters, shown on the dashboard’s approval screen and stored as the name of the key it eventually mints.
device_code is the 256-bit secret the CLI polls with; it is never shown to a human. user_code is the 8-character code (from a 20-character no-vowel, no-lookalike alphabet) a person types or confirms in the browser. The authorization expires in expires_in seconds (900, 15 minutes) if nobody approves or denies it.

Poll For Token

grant_type is optional (its absence is tolerated); when present it must be exactly urn:ietf:params:oauth:grant-type:device_code. Poll no more than once every interval seconds (5). On success, 200 OK:
The plaintext access_token is minted only at this point, inside the same transaction that consumes the authorization, so exactly one poll ever receives it. Every other outcome is 400 Bad Request with a bare { "error": "<code>" } body, deliberately not the platform’s usual error shape, so a poll loop can branch on the standard RFC 8628 code names: expired_token deliberately covers unknown and already-consumed codes as well as truly expired ones, so this endpoint is not an existence oracle for device_code values.

Look Up Device Authorization

Powers the dashboard’s confirm screen: ?user_code=BCDF-GHJK. Returns 404 Not Found for an unknown, expired, or already-decided code.

Approve Device Authorization

Approves as the signed-in caller. The minted key inherits the approver’s own role and tenant, exactly as POST /api/api-keys would. Returns 204 No Content, or 404 Not Found for an unknown or already-decided code.

Deny Device Authorization

Returns 204 No Content. Denial is terminal: the next poll reports access_denied once, and any poll after that reports expired_token.

Health & Metrics

Health Check

Returns text/plain ok. A liveness probe: it does not check dependencies.

Readiness Check

Readiness probe, distinct from /healthz’s liveness check.

Prometheus Metrics

Returns Prometheus text format metrics when the process metrics handler is mounted. The endpoint is gated behind a static bearer token: set METRICS_AUTH_TOKEN and send Authorization: Bearer <METRICS_AUTH_TOKEN>. The gate fails closed, so with no token configured every request, including unauthenticated ones, returns 401, and a missing or incorrect token also returns 401.
Last modified on September 6, 2026