Errors
Every /v1/ API error uses one envelope and one stable code value.
Envelope shape
{
"error": {
"type": "invalid_request_error",
"code": "unknown_filter",
"message": "'resistence' is not a filterable field on a motor. Filterable fields: current_max (or iq_max), kv, poles (or n), resistance (or R), weight.",
"param": "resistence[lte]",
"request_id": "req_2c5tQ...",
"doc_url": "https://thrustlab.com/docs/guides/errors#unknown_filter"
}
}
Envelope shape| Field | Notes |
|---|
type | The coarse taxonomic class for the error. |
code | The stable machine-readable string for the specific failure. |
message | Human-readable explanation. message may be reworded, so never branch on it. |
param | The request field that caused the error when present. null otherwise. |
request_id | The req_<ksuid> correlator to quote in a support ticket. |
doc_url | A link to the specific code row: https://thrustlab.com/docs/guides/errors#<code>. |
Some codes carry extra fields as siblings of the canonical ones. credits_insufficient carries required_amount and available_balance. spec_rate_limited carries retry_after_s, budget_per_hour, budget_per_day, remaining_hour, remaining_day. create_rate_limited carries retry_after_s, limit_per_minute, kind. solver_no_convergence carries error.details with nonconverged_rows, observed_rows, fraction, and limit.
Error types
Error types| Type | HTTP status | Retryable? | When |
|---|
invalid_request_error | 400 / 422 | No | Malformed input, validation failure, unknown enum value, or (422) a semantic precondition failure on an otherwise well-formed request. |
authentication_error | 401 | No | Missing, malformed, or invalid credential. |
permission_error | 403 | No | Authenticated but not authorized for this resource. |
not_found_error | 404 | No | Resource does not exist or is not visible to the caller. |
conflict_error | 409 | No (without input change) | Request conflicts with current resource state. |
idempotency_error | 409 | No (without a new key or matching body) | Idempotency-Key reused with a different request body. |
rate_limit_error | 429 | Yes (after Retry-After) | The flat per-credential request bucket, the per-account submission limit, the free-tier catalog filter budget, or the distinct-component datasheet budget. |
insufficient_credits_error | 402 | No (until balance changes) | Compute action requires more compute units than available. |
api_error | 500 / 503 | Yes | Server-side fault. The two expected 503 variants are billing_not_configured and read_only_maintenance. |
Every 429 response includes a Retry-After header in seconds. The four 429 codes are rate_limit_exceeded, create_rate_limited, filter_rate_limited, and spec_rate_limited. See rate limits for what each budget counts.
Four codes never appear as HTTP statuses: solver_no_convergence, solver_failure, propeller_geometry_missing, and sweep_points_persist_failed. Those are in the error object on a RUN RESOURCE whose status is "failed". Fetching that run returns HTTP 200 with the failure inside it. A client polling a run reads status first, then error.
Code catalog
Each row is generated from the server's own code registry, and each row id is the anchor an error's doc_url points at.
invalid_request_error (400 / 422)
authentication_error (401)
authentication_error error codes| Code | HTTP | Retryable | What it means |
|---|
UNAUTHORIZED | 401 | No | The credential was rejected on a first-party endpoint. |
invalid_api_key | 401 | No | The API key is unknown, revoked, or belongs to a disabled account. |
invalid_jwt | 401 | No | The bearer token is malformed, expired, or not an access token. |
invalid_jwt_subject | 401 | No | The token is valid but its subject is not an active user. |
missing_authorization | 401 | No | The request carried no Authorization header. |
permission_error (403)
permission_error error codes| Code | HTTP | Retryable | What it means |
|---|
FORBIDDEN | 403 | No | The caller may not act on this resource. |
challenge_failed | 403 | No | The verification challenge token was not accepted. |
challenge_required | 403 | No | Reading a catalog datasheet from a browser session needs a verification challenge; API-key callers never see this. |
email_unverified | 403 | No | The account must verify its email address before running a simulation. |
permission_denied | 403 | No | The caller may not modify this resource. |
pro_required | 403 | No | Multi-pack battery topology is a Pro feature. |
conflict_error (409)
conflict_error error codes| Code | HTTP | Retryable | What it means |
|---|
CONFLICT | 409 | No | The resource is not in a state that allows this action. |
active_simulation | 409 | No | The simulation is queued or running; cancel it before deleting. |
active_sweep | 409 | No | The sweep is queued or running; cancel it before deleting. |
already_running | 409 | No | The run is already executing. |
already_starred | 409 | No | The component is already starred in this project. |
already_terminal | 409 | No | The run has already finished, failed, or been canceled. |
component_in_use | 409 | No | The component is referenced by saved simulations; delete those first. |
delivery_not_terminal | 409 | No | The webhook delivery is still in flight; wait for it to settle before replaying. |
export_in_progress | 409 | Yes | An export of this run is already building; poll instead of starting another. |
export_not_ready | 409 | Yes | The export is still building; poll the run until it is ready. |
idempotency_request_in_progress | 409 | Yes | A request with this Idempotency-Key is still being processed; retry shortly to receive its result. |
project_has_active_runs | 409 | No | The project has runs that are still queued or running; cancel or wait for them before deleting it. |
submission_already_reviewed | 409 | No | The submission has already been accepted or rejected. |
rate_limit_error (429)
rate_limit_error error codes| Code | HTTP | Retryable | What it means |
|---|
create_rate_limited | 429 | Yes | The account submitted more runs of this kind in one minute than the submission limit allows; the response carries the limit and the wait. |
filter_rate_limited | 429 | Yes | The free tier's filtered catalog-query budget for this hour is spent. |
rate_limit_exceeded | 429 | Yes | The per-credential request bucket is empty; wait for `Retry-After` seconds. |
spec_rate_limited | 429 | Yes | The account has read datasheets for its budget of distinct components; the response carries the remaining budget and the reset. |
too_many_streams | 429 | Yes | This account has too many open live streams; close some before opening another. |
insufficient_credits_error (402)
insufficient_credits_error error codes| Code | HTTP | Retryable | What it means |
|---|
INSUFFICIENT_CREDITS | 402 | No | The account does not hold enough credits for this run. |
credits_insufficient | 402 | No | The account does not hold enough credits for this run. |
upgrade_required | 402 | No | The action needs a higher plan; the response names the gate and the cheapest plan that grants it. |
api_error (503 / in the run resource)
api_error error codes| Code | HTTP | Retryable | What it means |
|---|
billing_not_configured | 503 | Yes | Billing is not configured on this server. |
propeller_geometry_missing | in the run resource | No | The propeller spec does not describe a blade the solver can build. |
read_only_maintenance | 503 | Yes | New runs are paused for maintenance; reads and drafts still work. |
solver_failure | in the run resource | No | The run failed for a reason with no more specific code; the details stay in the server logs. |
solver_no_convergence | in the run resource | No | Too many points of the run did not converge for the results to be physical, so the run was rejected and the credits refunded. |
sweep_points_persist_failed | in the run resource | No | The sweep solved but its point rows could not be stored, so the results are incomplete. |
Reading errors in client code
The recommended pattern across every official SDK:
- Branch on
type for the high-level class of failure (auth vs validation vs not-found vs rate-limit).
- Branch on
code only when the user-visible path differs within that type.
- Treat the
code set as open, and fall back to the type handler for unknown codes.
- Always log
request_id.
- On 429, wait the number of seconds in
Retry-After. Do not use a fixed backoff.
For rate-limit and idempotency rules, see stability policy. For the 402 insufficient-balance envelope, see compute units, and for rate budgets, see rate limits.