ThrustLab

Errors

Every error returned by the /v1/ API uses a Stripe-style envelope. The HTTP status code, the type field, and the code field together tell you what happened and what to do about it. This page is the central catalog of every code value the API can return.

Envelope shape

{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_email",
    "message": "Email is not a valid address.",
    "param": "email",
    "request_id": "req_2c5tQ...",
    "doc_url": "https://docs.thrustlab.com/errors/invalid_email"
  }
}
FieldNotes
typeOne of the nine taxonomic types in the table below. Coarse classifier — branch on this for retry vs surface vs fix-input decisions.
codeStable machine-readable string identifying the specific failure. Add new codes within an existing type is non-breaking; changing the code for a given outcome is breaking.
messageHuman-readable explanation. Safe to show to end users, but copy may evolve — never branch on message text.
paramThe request field that triggered the error, when applicable. null for non-field errors.
request_idThe req_<ksuid> correlator. Include this in any support ticket.
doc_urlStable docs link for the specific code.

Some error codes carry additional fields beyond the envelope (e.g. credits_insufficient includes required_amount and available_balance). Those extensions are documented under the relevant resource page.

Error types

TypeHTTP statusRetryable?When
invalid_request_error400 / 422NoMalformed input, validation failure, unknown enum value, or (422) a semantic precondition failure on an otherwise well-formed request.
authentication_error401NoMissing, malformed, or invalid credential.
permission_error403NoAuthenticated but not authorized for this resource.
not_found_error404NoResource does not exist or is not visible to the caller.
conflict_error409No (without input change)Request conflicts with current resource state.
idempotency_error409No (without a new key or matching body)Idempotency-Key reused with a different request body.
rate_limit_error429Yes (after Retry-After)Credential bucket exhausted.
insufficient_credits_error402No (until balance changes)Compute action requires more compute units than available.
api_error500 / 503YesServer-side fault. 503 billing_not_configured is the one expected variant.

Code catalog

invalid_request_error (400)

codeSource
request_validation_failedPydantic body/query/path validation. Generic catch-all for shape errors.
invalid_cursorcursor query param does not decode to a valid keyset position.
invalid_filter_valueBracket-syntax filter value failed numeric coercion or was non-finite.
invalid_emailPATCH /v1/users/me with malformed email address.
idempotency_key_too_longIdempotency-Key header exceeds 255 characters.

authentication_error (401)

codeSource
missing_authorizationNo Authorization header was sent.
malformed_authorizationHeader present but not in Bearer <value> form.
invalid_credential_formatBearer value is neither a JWT nor a key_-prefixed string.
invalid_api_keyAPI key not found, revoked, or owned by an inactive user.
invalid_jwtJWT failed decode, signature check, or expiration.
invalid_jwt_subjectJWT decoded, but its sub claim does not resolve to an active user.

The same code (invalid_api_key) covers "doesn't exist", "revoked", and "owner deactivated" — this is deliberate, to prevent enumeration attacks.

permission_error (403)

codeSource
permission_deniedAuthenticated user lacks permission for the target resource. Examples: cross-user PATCH/DELETE on a private component; non-staff PATCH/DELETE on a public catalog component.

not_found_error (404)

codeSource
user_not_foundDefensive: authenticated user vanished mid-request.
project_not_foundGET/PATCH/DELETE /v1/projects/{id}. Cross-user access returns this same code (anti-enumeration).
component_not_foundGET/PATCH/DELETE /v1/components/{id}. Cross-user on a private component returns this same code.
submission_not_foundGET/PATCH/DELETE /v1/submissions/{id}. Cross-user returns this same code.
starred_component_not_foundDELETE /v1/starred_components/{id} for an unknown or cross-user star.
geometry_style_not_foundGET /v1/geometry_styles/{id} (or POST /v1/geometry/preview referencing an unknown style slug).
event_not_foundGET /v1/events/{id} for an event past 30-day retention or owned by another user.
webhook_endpoint_not_foundOperations on an unknown or cross-user wh_<ksuid> endpoint.
webhook_delivery_not_foundOperations on an unknown delivery row.

conflict_error (409)

codeSource
email_already_takenPATCH /v1/users/me with an email already registered to another user.
submission_already_reviewedPATCH/DELETE /v1/submissions/{id} after the submission has moved past status="submitted".
already_starredPOST /v1/starred_components for a (project_id, component_id) pair already starred.
delivery_not_terminalPOST /v1/webhook_endpoints/{id}/deliveries/{whd}/retry while the source delivery is still pending.

idempotency_error (409)

codeSource
idempotency_key_reused_with_different_bodyIdempotency-Key header reused on a write request with a request body that doesn't match the original request.

rate_limit_error (429)

codeSource
rate_limit_exceededPer-credential token bucket exhausted (1000 req/min sustained, 50 burst). Honor the Retry-After header.

insufficient_credits_error (402)

codeSource
credits_insufficientSimulation or sweep submission would exceed the caller's usable balance. Includes required_amount and available_balance extension fields.

api_error (500 / 503)

codeSource
internal_error500 — unexpected server-side fault. Carries a request_id for support correlation.
billing_not_configured503 — Stripe credentials not set on this server. Affects POST /v1/subscriptions, DELETE /v1/subscriptions/me, POST /v1/billing_portal/sessions. DB-only reads (/v1/compute-units/*, GET /v1/subscriptions/me) are unaffected.

Reading errors in client code

The recommended pattern across every official SDK:

  1. Branch on type for the high-level class of failure (auth vs validation vs not-found vs rate-limit etc.).
  2. Branch on code only when the user-facing message or recovery path differs within that type.
  3. Treat the code set as open. Adding a new code inside an existing type is non-breaking — the SDK should fall back to the type-level handler for unknown codes rather than crash.
  4. Always log request_id on error. It's the fastest way to find the failing request in the server logs when you open a support ticket.

For the rate-limit and idempotency semantics that govern when retries are safe, see stability-policy.md. For the 402 insufficient-balance envelope, see compute units.

We use cookies and analytics tools to understand how you use ThrustLab and improve the experience. Privacy Policy

Errors · ThrustLab API