Skip to documentation

API & MCP

Errors & rate limits

Both the programmatic /api/v1 surface and the in-app /api/w/:workspaceId BFF use consistent HTTP status codes and a small JSON error body. Errors are data, not surprises — handle them by status.

Error body

Errors return a JSON object with a machine-readable error string. Validation failures additionally include the zod issues array.

shapesjson
{ "error": "unauthorized" }
{ "error": "insufficient_scope" }
{ "error": "not_found" }
{ "error": "no-model-configured" }
{ "error": "Invalid request body", "issues": [ { "path": ["input"], "message": "…" } ] }

Status codes

StatusBodyWhen
400"Invalid request body" / "Invalid JSON body"Body fails zod validation or isn't valid JSON. issues[] pinpoints the field.
401"unauthorized"Missing, malformed, unknown, or revoked Bearer key (/api/v1); unauthenticated session (BFF).
402"spend_cap_exceeded"A UI, API, or scheduled agent start would exceed the workspace monthly spend cap.
403"insufficient_scope" / "insufficient_capability"The key lacks its declared scope, or a user key owner's live role no longer grants the operation.
404"not_found"Unknown id — including a run/agent in another workspace, which is invisible under RLS.
409"no-model-configured"The target model slot has no enabled provider (NotConfiguredError).
429"too_many_runs"The workspace active queued/running agent cap is full. This is admission control, not a general request throttle.

Cross-workspace reads are 404, never 403

A run or agent that belongs to a different workspace doesn't return "forbidden" — it simply doesn't exist for your key. Row-level security scopes the read to zero rows, and the handler returns 404 not_found. This is intentional: it never confirms the existence of another tenant's resource.

Access errors (BFF)

In-app routes guard with requireWorkspaceAccess(workspaceId), which throws a WorkspaceAccessError carrying a .status (typically 401 for no session or 403 for a non-member). Handlers map that status straight through, so a non-member gets a clean 403 rather than a stack trace.

The 409 setup path

409 no-model-configuredis the "you haven't finished setup" signal. On the JSON run path it's a 409 body; on the SSE stream path it arrives as an error event with message no-model-configured. Resolve it by binding a provider to the slot — see Providers & keys.

Rate limits

There is no general per-key request-rate limiter on /api/v1. Agent starts do have explicit admission responses: 402 spend_cap_exceeded and 429 too_many_runs. Clients should back off and retry 429 only after capacity becomes available; 402 requires a budget/configuration decision.

Protection against runaway spend is real but works through budgets, not HTTP rejections:

  • Per-run budgets — steps, tokens, and wall-clock per agent run. Exhaustion is not an error: the run ends budget_exceeded with the partial answer delivered.
  • Workspace monthly cap — one USD ceiling applies before UI, API, and scheduled starts. Over the cap, admission returns 402 (or skips scheduled work) and raises a deduplicated budget.cap alert.
  • Active-run cap — queued and running work consume the workspace concurrency limit; suspended runs do not. A full cap returns 429.
  • Attribution — every keyed run is tagged with its api_key_id, so the cost dashboard can answer "which integration spent this".

See Cost & schedules for the accounting model.