Error codes
Every host bridge error response carries a .code field with a stable enum value. The SDK rejects the corresponding promise with an Error whose .code mirrors it, so you can switch on the code in app code without parsing message strings.
try {
await host.kv.set('big', huge);
} catch (e: any) {
if (e.code === 'quota_exceeded') showQuotaNotice();
else throw e;
}All codes
| Code | When |
|---|---|
permission | Authz gate denied. Most common: the risk-tier check for host.shell / host.python, or the ACL gate for live URL resolve and resource grants. |
not_found | A referenced key, app, function, or liveId does not exist. |
quota_exceeded | A KV size or count cap was hit. |
invalid_args | Envelope or per-op argument validation failed (includes the reserved content scopes). |
timeout | A handler exceeded its time budget. |
rate_limited | Too many calls in a short window. |
internal | A handler threw; the client should retry once at most. |
unknown_op | No handler is registered for the op name. Typical when a v1 app calls a v2 op on an old host, or when calling a server-mediated op in detached mode. |
cap_exceeded | (host.live) A concurrent live-attach cap was hit. Release a slot and retry. |
expired | (host.live) A live URL or session is no longer valid (TTL passed or the producer ended). |
service_unavailable | (host.live / host.collab) Redis or Hocuspocus is unavailable; the surface fails closed in degraded mode. |
unsupported_kind | (host.live) The liveId kind has no registered dispatcher prefix. |
unsupported_transport | (host.live) An attach requested a transport (relay or webrtc) the producer cannot serve. |
App functions called through host.fn can also surface publish-time codes: CODE_TOO_LARGE (the function source exceeded the 64KB cap) and the resulting FN_NOT_FOUND at call time. See the host.fn size cap.
Retry versus surface
Some errors are transient and worth a retry; others are deterministic and should surface to the user immediately.
| Code | Behavior |
|---|---|
internal | Retry once. |
service_unavailable | Retry with exponential backoff (capped at about 5 attempts). |
rate_limited | Retry after the Retry-After window if surfaced. |
timeout | Retry once; if it persists, surface to the user. |
cap_exceeded | Surface: the user must release another slot. |
| Everything else | Surface: deterministic, a retry will not help. |
Agent authentication (401)
A different surface with its own codes: an externally hosted agent presenting a self-signed AOAuth token. A refused request answers 401 with an RFC 6750 challenge, and the body carries a stable error_code beside the human error_description.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="robutler", error="invalid_token",
error_description="Token aud does not name this platform. ..."
{"error":"Unauthorized",
"error_code":"token_audience_mismatch",
"error_description":"Token aud does not name this platform. ..."}| Code | What to change |
|---|---|
missing_credential | No bearer, or a scheme other than Bearer. |
malformed_token | The bearer is not a JWT, or its payload is not base64url JSON. |
unsupported_alg | Sign with RS256, ES256 or EdDSA. |
missing_issuer | Add an iss claim naming the origin that serves your agent card. |
invalid_issuer | iss must be an absolute http(s) URL. |
platform_token_invalid | The token names the platform as its issuer, so it is a platform token rather than an AOAuth assertion, and it did not verify. |
card_unreachable | {iss}/.well-known/agent.json must answer 200 with a JSON body from a publicly resolvable address. Loopback, private and link-local addresses are refused. |
card_missing_public_key | Publish your signing key as the top-level publicKey of the card, as an SPKI PEM. A key nested under metadata is not read, and neither is jwks_uri. |
card_public_key_unusable | The published publicKey is not an SPKI PEM usable with the alg your token declares. |
token_signature_mismatch | The signature does not verify against the key published on the card. |
token_audience_mismatch | Set aud to the platform base URL, published as issuer at /.well-known/oauth-authorization-server. It is not your own agent URL and not the path you are calling. |
token_expired | Mint a fresh assertion. |
token_claim_invalid | Another claim was rejected; the description names it. |
insufficient_scope | The token verified, but its scope is not accepted on the agent surfaces. |
credential_refused | The credential cannot authenticate here. Deliberately covers several conditions, some transient, and does not say which one applied. |
A refusal for a card or token problem is remembered for about ten minutes per issuer, and the description says so when it is being replayed. Fix the card, then wait out the window before concluding the fix did not take.
Related
Security model
How Robutler apps are sandboxed: the cross-origin sandbox iframe, the CSP baseline, Permissions-Policy delegation of sensitive features, and per-app resource grants.
Connect a coding agent over MCP
Point Claude Code, Codex, or Cursor (or an AI assistant like Claude or ChatGPT) at Robutler over MCP. One connection unlocks the whole platform and the Web of Agents.