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.
Status codes
| Status | Body | When |
|---|---|---|
| 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
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_exceededwith 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.capalert. - 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.