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"
}
}
| 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 include extra fields alongside the standard ones. credits_insufficient includes required_amount and available_balance. create_rate_limited includes retry_after_s, limit_per_minute, and kind. rate_limit_exceeded includes retry_after_s on the unauthenticated /v1/public/ routes. solver_no_convergence includes error.details with nonconverged_rows, observed_rows, fraction, and limit.
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-IP window on the unauthenticated /v1/public/ routes, the per-account submission limit, or the open-stream limit. |
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 three 429 codes are rate_limit_exceeded, create_rate_limited, and too_many_streams. 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)
| Code | HTTP | Retryable | What it means |
|---|---|---|---|
VALIDATION_ERROR | 422 | No | A well-formed request violates a domain rule; the message names it. |
account_required | 400 | No | That field saves something against an account, which an anonymous run does not have. |
airfoil_fit_failed | 422 | No | The uploaded coordinates could not be fitted to a usable section. |
already_dispatched | 400 | No | That run was already handed to a worker and cannot be dispatched again. |
dat_too_large | 400 | No | The uploaded airfoil coordinate file is above the size limit. |
dynamic_limit_exceeded | 422 | No | Rotors times save steps exceeds the plan's dynamic ceiling. |
events_required | 400 | No | A webhook endpoint must subscribe to at least one event type. |
geometry_not_loftable | 422 | No | The blade sections cannot be lofted into a solid. |
idempotency_key_required | 400 | No | Every write request needs an Idempotency-Key header. |
idempotency_key_too_long | 400 | No | The Idempotency-Key header is longer than 255 characters. |
invalid_analysis_request | 400 | No | The geometry analysis request is missing or contradicts a required field. |
invalid_component_axis | 400 | No | A sweep component axis targets an out-of-range rotor slot or mixes component types. |
invalid_component_id | 400 | No | A component id is malformed or names a component of the wrong type. |
invalid_component_spec | 400 | No | The inline component specification is nonfinite, too large, or nested too deeply. |
invalid_created_at | 400 | No | A `created_at` filter is not a valid timestamp. |
invalid_cursor | 400 | No | The pagination cursor is malformed, or was issued for a different sort than the one requested. |
invalid_dat | 400 | No | The uploaded airfoil coordinate file could not be parsed. |
invalid_email | 400 | No | The supplied email address is not a valid address. |
invalid_filter_value | 400 | No | A range-filter value is not a finite number. |
invalid_geometry | 422 | No | A propeller geometry is outside the bounds the solver accepts. |
invalid_limit | 400 | No | The `limit` parameter is outside the accepted range. |
invalid_pack_component | 422 | No | A battery in the pack topology is missing a required cell property. |
invalid_project | 400 | No | The project id is malformed or names no project this account owns. |
invalid_request | 400 | No | A query parameter or body field is outside its accepted range or vocabulary. |
invalid_rotation | 400 | No | A propeller rotation label is outside the accepted vocabulary. |
invalid_schedule | 422 | No | The dynamic control schedule is incomplete or inconsistent. |
invalid_status | 400 | No | The run is not in a status this action accepts. |
invalid_status_filter | 400 | No | The `status` filter is not one of the published run statuses. |
invalid_sweep_config | 400 | No | The sweep axes do not form a valid grid; the message names the failing axis. |
pack_cell_limit_exceeded | 422 | No | The pack topology holds more cells than the engine supports. |
pack_too_large_for_dynamic | 422 | No | The pack is above the cell ceiling for a transient run; use a steady-state run or a smaller pack. |
parallel_voltage_mismatch | 422 | No | Packs wired in parallel must have the same series cell count. |
propeller_label_unavailable | 422 | No | The Fast model needs a printed propeller size and could not read one from the selected catalog propeller; send propeller_label, or run with propeller_model='full'. |
propeller_model_unsupported | 422 | No | The requested propeller model does not run this kind of job; dynamic and batch runs need propeller_model='full'. |
request_body_too_large | 400 | No | The request body exceeds the maximum size accepted for an idempotent request. |
request_validation_failed | 400 | No | The request body or query string failed schema validation; `param` names the offending field. |
rotor_limit_exceeded | 422 | No | The run has more rotors than the plan allows. |
simulation_not_exportable | 422 | No | This run's shape is not supported by the requested export format. |
sweep_limit_exceeded | 422 | No | Rotors times points exceeds the plan's sweep ceiling. |
sweep_too_large | 400 | No | The sweep grid exceeds the 10,000-point ceiling for one submission. |
sweep_too_small | 400 | No | A sweep must define at least two evaluation points. |
unknown_airfoil | 422 | No | An airfoil reference names no airfoil this account can use. |
unknown_event_type | 400 | No | A subscribed event type is not one the API emits. |
unknown_filter | 400 | No | A bracket filter names a field this component type does not have; the message lists the fields it does have. |
webhook_url_dns_failure | 400 | Yes | The webhook hostname did not resolve at the time of the request. |
webhook_url_invalid | 400 | No | The webhook URL has no hostname. |
webhook_url_not_https | 400 | No | A webhook URL must use https. |
webhook_url_not_publicly_routable | 400 | No | The webhook hostname resolves to a private or reserved address. |
authentication_error (401)
| 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)
| Code | HTTP | Retryable | What it means |
|---|---|---|---|
FORBIDDEN | 403 | No | The caller may not act on this resource. |
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. |
not_found_error (404)
| Code | HTTP | Retryable | What it means |
|---|---|---|---|
NOT_FOUND | 404 | No | The referenced record does not exist or is not visible to this account. |
airfoil_not_found | 404 | No | No airfoil with that id is visible to this account. |
component_not_found | 404 | No | No component with that id is visible to this account. |
credit_transaction_not_found | 404 | No | No credit transaction with that id exists. |
event_not_found | 404 | No | No event with that id belongs to this account. |
export_expired | 404 | Yes | The export download link has expired; request the export again. |
geometry_style_not_found | 404 | No | No geometry style with that id exists. |
not_found | 404 | No | No published resource matches that identifier. |
project_not_found | 404 | No | No project with that id belongs to this account. |
resource_missing | 404 | No | No run with that id belongs to this account. |
starred_component_not_found | 404 | No | That component is not starred in this project. |
submission_not_found | 404 | No | No component submission with that id exists. |
user_not_found | 404 | No | The authenticated user record could not be loaded. |
webhook_delivery_not_found | 404 | No | No webhook delivery with that id belongs to this account. |
webhook_endpoint_not_found | 404 | No | No webhook endpoint with that id belongs to this account. |
conflict_error (409)
| 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_claimed | 409 | No | That anonymous run has already been saved to another account. |
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. |
idempotency_error (409)
| Code | HTTP | Retryable | What it means |
|---|---|---|---|
idempotency_key_reused_with_different_body | 409 | No | The Idempotency-Key was already used with a different request body. |
rate_limit_error (429)
| Code | HTTP | Retryable | What it means |
|---|---|---|---|
account_queue_full | 429 | Yes | The account already has as many simulations queued or running as its plan allows at once; the response carries the limit and the count. Wait for some to finish, or cancel queued runs, then resubmit. |
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. |
rate_limit_exceeded | 429 | Yes | The per-credential request bucket is empty; wait for `Retry-After` seconds. |
too_many_streams | 429 | Yes | This account has too many open live streams; close some before opening another. |
insufficient_credits_error (402)
| 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)
| Code | HTTP | Retryable | What it means |
|---|---|---|---|
billing_not_configured | 503 | Yes | Billing is not configured on this server. |
component_analysis_capacity | in the run resource | Yes | The component-analysis job exhausted its capacity retries; submit again when a worker slot is available. |
component_analysis_failed | in the run resource | No | The component-analysis job failed; inspect its inputs or contact support with the job ID. |
evaluation_failed | in the run resource | No | One candidate or operating point failed during component analysis; inspect its inputs and diagnostics. |
invalid_operating_point | in the run resource | No | The operating point did not converge, exceeds component limits, or has an incomplete dynamic trajectory. |
propeller_geometry_missing | in the run resource | No | The propeller spec does not describe a blade the solver can build. |
public_simulate_busy | 503 | Yes | Every free anonymous-solver slot is in use; the response carries a Retry-After and the identical request succeeds once one frees. |
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
typefor the high-level class of failure (auth vs validation vs not-found vs rate-limit). - Branch on
codeonly when the user-visible path differs within that type. - Treat the
codeset as open, and fall back to thetypehandler 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.