API Errors
Every error from the Varity platform API (https://varity.app/api) returns the same JSON envelope. Program against code and retryable; show message and action to humans.
The Error Envelope
Section titled “The Error Envelope”{ "code": "authentication_required", "message": "A Bearer API key is required.", "action": "Send Authorization: Bearer <VARITY_API_KEY>.", "retryable": false, "docs_url": "https://docs.varity.so/api/errors#authentication_required", "correlation_id": "3e0dbab4-bb81-4e95-a9b6-baefeff0e52e"}| Field | Type | Always present | Meaning |
|---|---|---|---|
code | string | yes | Stable machine-readable identifier. Branch on this, never on message. |
message | string | yes | Human-readable statement of what went wrong. |
action | string | yes | What the caller should actually do next. |
retryable | boolean | yes | Whether retrying the same request can succeed. |
docs_url | string (uri) | yes | Deep link to this page, anchored at the code. |
field | string | no | The offending request field, on validation failures. |
correlation_id | string | no | Request correlation identifier. See below. |
Caller Fault, Platform Fault, Provider Fault
Section titled “Caller Fault, Platform Fault, Provider Fault”The single most useful thing about a Varity error is whose problem it is. The HTTP status tells you the class; retryable tells you whether to try again.
Caller fault: 4xx, retryable: false. Your request is wrong. Retrying the identical request produces the identical error. Fix the request, the credential, or the account state. This covers bad input (400, 422), authentication (401), scope (403), missing resources (404), and conflicts (409).
Platform fault: 5xx, retryable: true. A Varity component could not answer. The request may still be valid. Retry with exponential backoff and jitter.
Provider fault: 5xx and 409, retryable: true. A downstream compute or inference supplier was unavailable or out of capacity. Varity does not expose supplier identity in the public contract; you see it as an unavailable-or-capacity code. Retry with backoff, and for capacity errors consider a different profile.
The reliable rule: retry when and only when retryable is true. Do not infer retryability from the status code alone. 409 gpu_capacity_unavailable is retryable while 409 idempotency_key_reused is not.
Error Codes
Section titled “Error Codes”Every code below is emitted by the live gateway. Statuses and retryable values are as implemented.
Authentication And Authorization
Section titled “Authentication And Authorization”| Code | HTTP | Retryable | Cause |
|---|---|---|---|
authentication_required | 401 | no | No Authorization: Bearer header was sent. |
invalid_api_key | 401 | no | The API key is invalid or revoked. |
api_key_owner_unbound | 401 | no | The key is not bound to an owner account. |
per_user_api_key_required | 401 | no | The route requires a per-user API key. |
credential_scope_violation | 403 | no | The credential is valid but not scoped to this product or resource. |
portal_session_required | 403 | no | The route requires a Developer Portal session, not an API key. |
api_key_auth_unavailable | 503 | yes | The key authentication service could not be reached. |
api_key_verification_unavailable | 502/503 | yes | Key verification is temporarily unavailable. |
Request Validation
Section titled “Request Validation”| Code | HTTP | Retryable | Cause |
|---|---|---|---|
invalid_env | 400 | no | Malformed environment-variable update payload. |
invalid_idempotency_key | 400 | no | The key does not satisfy the called route’s OpenAPI parameter. Machine operations use [A-Za-z0-9._:-]{8,128}; deployment and attachment routes have different bounds. |
invalid_run_id | 400 | no | Malformed run identifier. |
invalid_webhook_url | 400 | no | The webhook URL is not a public HTTPS URL. |
invalid_webhook_id | 400 | no | Malformed webhook identifier. |
invalid_api_key_id | 400 | no | Malformed API key identifier. |
invalid_inference_key_request | 400 | no | Malformed AI Gateway key request. |
invalid_ai_attachment_request | 400 | no | Malformed AI Gateway attachment request. |
preset_unavailable | 400 | no | The requested resource preset cannot be served. |
accelerator_profile_invalid | 422 | no | The accelerator profile selection is not valid. |
invalid_accelerator_quote_request | 422 | no | The GPU quote request failed validation. |
machine_request_invalid | 422 | no | The machine create request failed validation. |
machine_quote_request_invalid | 422 | no | The machine quote request failed validation. |
deployment_profile_scope_invalid | 422 | no | workload and execution_class do not form a valid scope. |
deployment_profile_contract_invalid | 422 | no | The requested profile contract version is not valid. |
unsupported_deployment_request | 400/422 | no | The deployment request shape is not supported. |
Validation errors may carry a field naming the offending property.
Not Found And Conflict
Section titled “Not Found And Conflict”| Code | HTTP | Retryable | Cause |
|---|---|---|---|
not_found | 404 | no | The resource does not exist or is not owned by you. |
deployment_not_found | 404 | no | No such deployment for this owner. |
deployment_run_not_found | 404 | no | No such deployment run. |
template_not_found | 404 | no | No such template. |
webhook_not_found | 404 | no | No such webhook. |
idempotency_key_reused | 409 | no | The key was reused with a different request body. |
legacy_deployment_retired | 409 | no | The deployment predates the current runtime contract. |
legacy_run_retired | 410 | no | The run record is retired and permanently gone. |
restart_unavailable | 409/501/503 | varies | Restart is not possible for this deployment. 503 is retryable; 409 and 501 are not. |
template_not_certified | 409/503 | varies | The template is not certified, or certification could not be checked. 503 is retryable; 409 is not. |
Capacity And Billing
Section titled “Capacity And Billing”Billing refusals are 402 and a billing outage is 503. These codes are the complete set every Varity service uses, rendered from the billing contract. A refused action starts nothing and charges nothing.
| Code | HTTP | Retryable | Meaning |
|---|---|---|---|
insufficient_credits | 402 | no | Your balance cannot cover the action. Add funds or change plan. Replaces the AI Gateway code insufficient_gateway_balance; handle both while it is retired. |
daily_free_limit_reached | 402 | no | The Free plan's daily AI allowance is used up. It resets the next UTC day; any subscription or top-up removes the limit. |
spending_limit_reached | 402 | no | The monthly spending limit you set is reached. Raise the limit or wait for the next month. Replaces the AI Gateway code gateway_monthly_limit_exceeded; handle both while it is retired. |
subscription_required | 402 | no | The action needs a paid balance from a subscription or top-up, for example an AI Gateway API key request on Free plan usage only. |
wallet_frozen | 402 | no | Paid usage on this account is paused, for example while a refund or dispute leaves a balance owed. New funding repays it first. |
payment_method_shared | 402 | no | This card already backs another account's free app. Use a different card. |
quote_expired | 402 | no | The price quote expired. Request a new quote and confirm again. |
billing_unavailable | 503 | yes | Billing state could not be read. Never reported as a zero balance; retry with backoff. The body can be the short form {"error": "billing_unavailable"}, so read the code from error when code is absent. |
| Code | HTTP | Retryable | Cause |
|---|---|---|---|
gpu_capacity_unavailable | 409 | yes | No accelerator capacity right now for the requested profile. |
gpu_capacity_unavailable is the one common 409 you should retry. Capacity is a moving target; back off and retry, or quote a different profile.
Upstream And Platform Unavailability
Section titled “Upstream And Platform Unavailability”All of these are retryable: true. They mean a Varity subsystem or a downstream provider did not answer, not that your request was wrong.
| Code | HTTP | Cause |
|---|---|---|
deploy_service_unavailable | 502/503 | The deploy service could not be reached. |
deploy_status_unavailable | 502 | Run status could not be read. |
deployment_read_unavailable | 502 | The deployment record could not be read. |
logs_unavailable | 502/503 | The log source could not be reached. |
credit_state_unavailable | 503 | Balance state could not be read. |
billing_summary_unavailable | 503 | The billing summary could not be read. |
webhook_state_unavailable | 503 | Webhook state could not be read. |
custom_domain_unavailable | 503 | Custom-domain state could not be read. |
gpu_quote_unavailable | 503 | A GPU quote could not be issued. |
idempotency_unavailable | 503 | The idempotency store could not be reached. |
api_key_management_unavailable | 502/503 | Key management could not be reached. |
ai_attachment_unavailable | 502 | AI Gateway attachment state could not be read. |
env_update_failed | 5xx (varies) | The environment update did not apply. |
redeploy_failed | 5xx (varies) | The redeploy did not start. |
deployment_delete_failed | 5xx (varies) | The delete did not complete. |
Queue Backpressure
Section titled “Queue Backpressure”| Code | HTTP | Retryable | Cause |
|---|---|---|---|
queue_saturated | 429 | yes | The platform-wide deployment queue is full. Nothing was allocated or charged. |
Wait a few seconds and retry with the same Idempotency-Key. A plain-text 429 without this envelope comes from the edge’s abuse protection instead. See Rate Limits.
Responses That Do Not Use The Standard Envelope
Section titled “Responses That Do Not Use The Standard Envelope”A few routes predate the envelope and answer with their own shape. They still carry a code, but not retryable or docs_url. Parse defensively: read code from the top level, and fall back to error.code and detail.code.
GET /api/deployments/{id}/health-history on failure returns 503 with the code nested under error:
{ "error": { "code": "history_unavailable", "message": "Runtime history is temporarily unavailable.", "retryable": true } }An unrepresentable deploy request returns 422 with the code nested under detail:
{ "detail": { "stage": "acceptance", "code": "unsupported_deployment_request", "message": "This deploy request cannot be represented by the durable operation engine." } }An unknown pricing profile or preset returns 400 and fails closed by returning the valid menu, using error in place of message:
{ "code": "profile_unknown", "error": "Unknown pricing profile \"bogus\"", "profiles": ["web-app"], "presets": ["starter"] }The preset_unknown code follows the same shape. All of these are caller-fault except history_unavailable, which is retryable.
These codes are real and you will see them, but they are not in the table above because they do not use the envelope:
| code | HTTP | shape | retryable |
|---|---|---|---|
profile_unknown | 400 | top-level code + error, plus the valid profiles and presets menu | no |
preset_unknown | 400 | top-level code + error, plus the valid presets menu | no |
history_unavailable | 503 | nested under error | yes |
unsupported_deployment_request | 422 | nested under detail | no |
Partial Reads Are Not Errors
Section titled “Partial Reads Are Not Errors”Log, event, and health reads return 200 with a complete flag rather than failing when the live source is briefly unavailable. When complete is false the window may be partial and a warning_code is set (logs_incomplete, events_incomplete). This is a successful response describing an incomplete window. Retry the read for a complete one; do not treat it as a failure.
The exception is a deployment with no runtime at all, such as a static site: its log read is a definite, non-retryable 409 runtime_not_started rather than an incomplete window, because retrying can never produce runtime logs. See Public API Reference.
Correlation IDs
Section titled “Correlation IDs”Every error carries a correlation_id when one is available. It is the single most useful thing you can give support.
Supply your own. Send an X-Request-Id header and the platform echoes that exact value back as correlation_id:
curl https://varity.app/api/credits \ -H "Authorization: Bearer $VARITY_API_KEY" \ -H "X-Request-Id: my-service-20260822-0001"{ "code": "authentication_required", "correlation_id": "my-service-20260822-0001" }If you do not send one, the platform assigns a UUID. Log correlation_id on every non-2xx response alongside your own request context. When you open a support request, include it. With it, a failure is traceable end to end; without it, it usually is not.
Handling Errors In Code
Section titled “Handling Errors In Code”import time, random, requests
def call(method, url, **kw): for attempt in range(5): r = requests.request(method, url, **kw) if r.status_code < 400: return r.json()
err = r.json() if not err.get("retryable"): raise RuntimeError( f"{err['code']}: {err['message']} " f"({err['action']}) correlation_id={err.get('correlation_id')}" )
# Honor the server's window when it gives one. wait = float(r.headers.get("Retry-After", 2 ** attempt)) time.sleep(wait + random.uniform(0, 0.5))
raise RuntimeError("retries exhausted")Next Steps
Section titled “Next Steps”- Rate Limits: budgets, headers, and retry guidance
- Public API Reference: endpoints and examples
- AI Gateway: the OpenAI-compatible inference surface, which has its own error shape