Errors, limits and compute
Error format
Every API error uses the same envelope:
{
"error": {
"type": "validation_error",
"code": "unknown_inputs",
"message": "One or more inputs are not declared by this capability",
"param": "inputs",
"details": {"unknown": ["foo"]},
"request_id": "..."
}
}Every response includes a request ID. Include it when you contact support.
Errors from the managed-lab routes add a resolution, whether the call is retryable, and a gateway_request_id under details. Their codes match the MCP tools:
| Status | Codes | Meaning |
|---|---|---|
403 | insufficient_scope | The key lacks the route’s scope. |
403 | gateway_not_enabled | The call starts compute, publishes, changes a lab or reaches external sources, and managed operations are not enabled for the account. Reads still work. |
404 | run_not_found, workspace_not_found, lab_not_found, file_not_found, experiment_not_found | Not found. Records owned by another account get exactly this answer, so an ID never reveals whether someone else’s work exists. |
409 | plan_digest_mismatch, plan_expired, plan_blocked, plan_unavailable, idempotency_conflict | Prepare the plan again, or use the exact digest it returned. |
429 | rate_limited | The daily fair-use limit for this key. Wait Retry-After seconds. |
In Python, failed requests raise ApiError, AuthenticationError, ValidationError, RateLimitError or InsufficientCreditsError. A run that ends badly raises RunFailed, and a caller timeout raises RunTimeout with the live run handle.
Idempotency
POST /runs needs an Idempotency-Key header. The SDK creates one for you, or you can pass idempotency_key=.
| What happens | Response |
|---|---|
| Same key, same request | The original run |
| No key | 400 idempotency_key_required |
| Same key, different request | 409 idempotency_conflict |
| Same key while the first request is still processing | 409 idempotency_in_progress, retry after Retry-After |
Retries and rate limits
The SDK retries transport failures and HTTP 408, 425, 429, 500, 502, 503 and 504 with bounded exponential backoff and jitter, and it respects Retry-After. Don’t retry validation, authentication or idempotency-conflict errors without changing the request.
Rate limits apply per API key. Creating runs has a tighter limit than reads, and compute concurrency is limited per account. Check these headers:
RateLimit-Limit
RateLimit-Remaining
RateLimit-Reset
Retry-AfterCompute
Managed runs of a private Lab revision (kind: managed) consume compute. Their published price is shown before execution.
Scientific runs consume compute from your personal allowance or the owning team’s pool. Add compute in Billing; purchased funds are available only after Stripe confirms payment.
Before a run is queued, the service reserves enough compute for its compute profile. When the run ends it charges what was used and gives back the rest. The run response shows both amounts in US dollars (the field names keep their original credits_ prefix):
{
"credits_reserved": 10,
"credits_settled": 3
}If your available compute can’t cover the reservation, the request fails with 402 and Python raises InsufficientCreditsError.