Skip to content

API reference

Errors and headers

Errors come back in the shape your client already expects, with a stable type and a message in plain English. Headers that steer the gateway, and the ones it sends back.

#The error shape

JSON
{
  "error": {
    "type": "agent_stopped",
    "message": "this agent is stopped under an owner hold, so nothing it sends goes upstream and nothing was sent; the person it acts for, an owner or an admin can restart it in the console under Agents",
    "hold": "owner",
    "stopped": "agent",
    "agentId": "agt_4f2c91a7",
    "fix": "Stop: the kill switch is on (error.hold says which hold, error.message who may lift it). Do not retry; tell the person you act for.",
    "docs": "https://immiscible.fly.dev/docs/guides/kill-switch#what-a-stop-stops",
    "requestId": "d210346a-c44e-4be8-bca7-1d8503868ce8"
  }
}

requestId is the same id as the x-request-id response header, on every error: quote it when you write to us and we can find the request. Every 4xx carries fix, one sentence saying what to send differently, and docs, the page that explains it, so an agent can correct its next request from the error alone. Both are additions to the shape your client already reads; a route that has its own more specific fix keeps it. On the MCP server, JSON-RPC errors carry them in error.data, and a refused tool call puts them in the tool result’s text.

A validation failure also lists each problem with the field it is about, so a form can mark the field. message joins them with “; “:

JSON
{
  "error": {
    "type": "invalid_request",
    "message": "summary is required: say in one sentence what the agent is about to do; payment.amount must be a whole number of minor units (pence) above zero, for example 6420 for £64.20",
    "errors": [
      { "field": "summary", "message": "summary is required: say in one sentence what the agent is about to do" },
      { "field": "payment.amount", "message": "payment.amount must be a whole number of minor units (pence) above zero, for example 6420 for £64.20" }
    ]
  }
}

field is a dotted path into the body, or null when the problem is the body as a whole. invalid_request, invalid_mandate, invalid_budget and the other validation types (invalid_application, invalid_upstream) carry errors.

Every response, error or not, carries an x-request-id header. It is the id of the request in our logs: quote it when you ask for help with one.

On Anthropic-shaped routes errors use Anthropic’s envelope ({ "type": "error", "error": { ... } }), so SDK error handling keeps working. error.type is stable and safe to branch on; error.message is for people and may change. Some refusals carry the numbers a client needs to act (for example oldCount and newCount on selection_changed). Every refusal that reflects a policy decision carries an evidenceRecord id: the refusal itself is in the ledger.

#Common to every route

StatusTypeMeaning
400invalid_jsonthe body is not JSON
400invalid_requesta field is missing or malformed; errors lists each problem
401unauthenticated, invalid_api_keyno credential, or one that is unknown, expired or revoked
403forbiddenthe credential is valid but this caller may not do this; the message says why
403csrfa console write without x-immiscible-csrf, or from another origin
404not_foundno such object in this workspace (an id from another workspace is simply not found)
404route_not_foundno route has this method and path; didYouMean names the nearest real one, for example POST /v1/actions/authorize
405method_not_allowedthe path exists with other methods; the allow header lists them
403, 409separation_of_duties, second_owner_required, second_person_requiredanother person must do this
413body_too_largeover the limit: 1 MB for most routes, larger for model traffic and billing imports
429rate_limitedhonour retry-after
500internalour fault; in production the message says only “internal error”; send us the x-request-id

#The gate

StatusTypeWhat to do
400invalid_requestno type, no summary, a one-word type that is not built in (paymnt: the message suggests payment; custom types are dotted, such as crm.update), an amount that is not whole minor units, a currency that is not three letters; errors lists each
400invalid_status, invalid_amounta settlement needs completed, failed or cancelled, and whole minor units
402plan_limit_reachedthe plan’s agent or mandate allowance is used
400invalid_mandatecreating or changing a mandate: its limits, merchants, fields or actions are malformed; errors lists each
403agent_key_requiredthe key is not an agent key; agent routes need one (Agents, Add an agent, then Collect the agent’s key on its setup page)
403agent_not_foundan agent key whose agent has since been removed
200(deny, signal agent_frozen)the kill switch is on for this agent: a stopped agent’s action requests are decisions, not HTTP errors
404not_foundno action with that id belongs to this agent
409idempotency_conflictthe key was used for a different request
409confirm_broadena new rule would allow anywhere (any domain, any merchant) beside a narrower rule for the same agent; reason says which, and sending confirmBroaden: true saves it
409not_allowed, already_settledonly an allowed, unsettled action can be settled

#The gateway

StatusTypeWhat to do
400unclassified_requestsend x-immiscible-task-class or bind a default class to the key (enforce mode only)
400sensitive_data_blockedthe workspace’s DLP setting is block; the message counts what was found by kind, never the values
402approval_requiredthe budget’s approval step; the body names the owner
402seat_limit_reachedFree’s people limit is in use this month, and its seven days of grace have ended
403agent_stoppedthe key’s agent is stopped, or Stop every agent stands and the key is not bound to one agent; hold names the hold, message who may lift it, and nothing was sent (what a stop stops). The MCP proxy refuses the same way, in error.data
409policy_conflictno model satisfies the policy; relaxations lists what would admit one, at what cost, signed off by whom
429budget_exhaustedthe budget’s hard ceiling
429plan_limit_reachedthe plan’s monthly request allowance is used
502upstream_failureevery eligible model failed; attemptedModels lists them

#Machine admin

StatusTypeWhat to do
401invalid_token, token_expiredunknown, revoked or expired, or presented from an address the token is not pinned to
400invalid_budgetPOST /v1/admin/budgets: a scope, amount, period or ladder that is not valid; errors lists each
403missing_scopethe token lacks the scope this route needs
400template_only, unknown_templatea token attaches mandates from templates only
400invalid_selector, nothing_selected, reason_requireda bulk freeze needs a selector that matches and a reason
409confirm_count_required, selection_changedsend confirmCount equal to the current count
429drill_cooldowna token may start one drill an hour per workspace
202(pending)a freeze past a ceiling waits for a person; not an error, but nothing is frozen yet

#Headers

#Request

HeaderPurpose
x-immiscible-task-classwhat kind of work this is, for example code.feature, support.triage
x-immiscible-task-idgroup this call into an existing task
x-immiscible-objectivebalanced (default), cost, quality, latency or sovereign
x-immiscible-dry-runtrue to route and budget-check without calling a model
x-immiscible-human-oversightacknowledged for high-risk work a person reviews
x-immiscible-client-sessionthe agent’s session id, 1 to 128 printable characters; name the same id as session.id on action requests
idempotency-keythe action request’s idempotency key, if not in the body
traceparenta W3C trace to continue
x-immiscible-csrf1 on every console write

#Response

HeaderMeaning
x-request-idthis request’s id in our logs; quote it when asking for help
immiscible-versionthe API version that answered, as a date (2026-10-01). There is one version; changes within it only add (new operations, optional fields, new values to ignore when unknown). A change that removes or renames anything would come under a new date.
ratelimit-limit, ratelimit-remaining, ratelimit-reseton the gate’s routes, every answer: the limit per minute, what is left of it, and seconds until it is full again
allowon 405, the methods the path accepts
traceparentthe trace this request ran in; join your own spans to it
x-immiscible-sessiona server-issued session id for a client that named none; send it back
x-immiscible-task-idthe task this call was recorded against
x-immiscible-call-idthis call
x-immiscible-routed-tothe model that served it (enforce mode)
x-immiscible-would-route-towhat routing would have chosen (shadow mode)
x-immiscible-enforcementshadow, observe, nudge or downgrade
x-immiscible-cost-microscost in millionths of a US dollar, from reconciled usage
x-immiscible-jurisdictionserving domicile and data region
x-immiscible-budget-scope, x-immiscible-yield-multiplierthe budget that bound this request, and its multiplier
x-immiscible-advisorya plain-English note when a budget is close
retry-afterseconds to wait, on 429