Error codes
Every error the API returns — one shape, a fixed code list, and explicit guidance on what is retryable.
Every non-2xx response has the same shape, and every code on this page is in
the enum in /openapi.json — there are no undocumented
errors.
{
"error": {
"code": "budget_exceeded",
"message": "Run halted at max_budget_usd=500 after 74 episodes.",
"run_id": "run_8843",
"doc_url": "https://roborama.com/docs/errors/#budget_exceeded"
}
}
run_id appears when the error concerns a specific run. SDKs raise these as
typed exceptions carrying the same fields.
Auth and access
| Code | HTTP | Meaning | Retry? |
|---|---|---|---|
invalid_api_key | 401 | key missing, malformed, or revoked; live keys use the rbr_live_ prefix | no — fix the key |
insufficient_scope | 403 | key is valid but lacks the needed scope, e.g. runs:write | no — issue a key with the scope |
not_found | 404 | no such run, robot, environment, suite, or stream | no |
publish_forbidden | 403 | publish="leaderboard" requested without leaderboard opt-in on the account | no — opt in first |
rate_limited | 429 | request rate exceeded; Retry-After header is set | yes — after the header's delay |
Validation (submission rejected, nothing ran)
These fire before any motor moves; a rejected submission costs nothing.
| Code | HTTP | Meaning | Retry? |
|---|---|---|---|
contract_validation_failed | 422 | declared action_space or observation_contract doesn't match the pinned robot and scene (policies) | no — fix the declaration or the pin |
firmware_pin_unavailable | 422 | requested model@firmware isn't installed on any cell; check robots.list() firmwares | no — pin an available revision |
environment_unavailable | 422 | environment id or revision not in the catalogue, or offline for rebuild | no — check environments.list() |
task_unknown | 422 | task id or version not recognized, e.g. a typo in load_dishwasher@v2 | no |
scenario_format_invalid | 422 | verify() scenarios parse as none of: PolaRiS/Isaac/world-model exports, layout-replay specs | no — fix the file |
episodes_invalid | 422 | episodes is neither a positive int nor a well-formed auto(ci=..., moe=...) | no |
quote_expired | 409 | submission referenced a quote past its validity window | yes — re-quote and resubmit |
Runtime (the run itself)
| Code | HTTP | Meaning | Retry? |
|---|---|---|---|
budget_exceeded | 402 | max_budget_usd hard stop fired mid-run; partial results kept | resubmit with a higher cap if the interval is too wide |
monthly_budget_exceeded | 402 | account-level hard budget reached; submissions rejected, in-flight runs halted | no — raise the budget |
queue_timeout | 408 | job couldn't be scheduled within its priority tier's window | yes — resubmit, or use burst |
policy_endpoint_timeout | 408 | endpoint policy persistently exceeded latency_budget_ms; run halted | fix the endpoint, then resubmit |
estop_triggered | 409 | hardware or safety emergency stop during the run; also emitted as the estop.triggered webhook | contact support if unexpected |
retention_expired | 410 | artifacts past their data.retention window (or purged) | no — the data is gone, by design |
Handling the important one
budget_exceeded is the code every integration should handle deliberately,
because it carries a partial result with honest statistics — n and the
interval describe the episodes that ran.
handling budget_exceeded
import roborama # reads ROBORAMA_API_KEY from the environment
try:
run = roborama.run(
robot="g1-edu-pro@fw2.3",
environment="kitchen-std@v1.2",
task="pick_place@v1",
episodes=600,
max_budget_usd=500, # deliberately too small for 600 episodes
)
print(run.result())
except roborama.BudgetExceeded as err:
# budget_exceeded: the hard stop fired mid-run; partial results are kept
print(err.code, err.run_id)
partial = err.partial_result
print(partial.n, partial.success_rate, partial.ci95)