# Error registry

> Every error code with retry semantics — the docs_url targets.


Every error code the API can return, with retry semantics. This registry is closed and append-only — codes are never removed or repurposed within v1. Machine-readable at `GET https://api.primateintelligence.ai/v1/errors`.

**retryable** = safe to retry the same request unchanged (with backoff). **idem** = idempotent to retry with the same `Idempotency-Key`.

## HTTP errors

### `invalid_api_key`

<a id="invalid_api_key"></a>**HTTP 401** · retryable: **no** · idem: **no**

API key is invalid. Check the key, env var, and whitespace.

### `key_revoked`

<a id="key_revoked"></a>**HTTP 401** · retryable: **no** · idem: **no**

This API key has been revoked. Rotate or create a new key.

### `key_expired`

<a id="key_expired"></a>**HTTP 401** · retryable: **no** · idem: **no**

This API key has expired. Create a new key.

### `token_expired`

<a id="token_expired"></a>**HTTP 401** · retryable: **no** · idem: **no**

Client token has expired. Mint a new token from your server (client tokens are never refreshable).

### `insufficient_scope`

<a id="insufficient_scope"></a>**HTTP 403** · retryable: **no** · idem: **no**

The key does not carry the scope required for this operation.

### `account_restricted`

<a id="account_restricted"></a>**HTTP 403** · retryable: **no** · idem: **no**

Account is restricted. Complete signup/verification.

### `test_mode_only`

<a id="test_mode_only"></a>**HTTP 403** · retryable: **no** · idem: **no**

Operation not available in this key mode.

### `test_key_fixture_only`

<a id="test_key_fixture_only"></a>**HTTP 403** · retryable: **no** · idem: **no**

Test keys can only analyze the provided fixture video. Create a live key to analyze your own uploads.

### `validation_failed`

<a id="validation_failed"></a>**HTTP 400** · retryable: **no** · idem: **no**

Request validation failed. All violations are listed in error.errors[].

### `unsupported_media_type`

<a id="unsupported_media_type"></a>**HTTP 415** · retryable: **no** · idem: **no**

Send video/mp4 or video/quicktime with a correct Content-Type.

### `payload_too_large`

<a id="payload_too_large"></a>**HTTP 413** · retryable: **no** · idem: **no**

Payload exceeds the 100MB multipart cap. Use the presigned upload path.

### `resource_not_found`

<a id="resource_not_found"></a>**HTTP 404** · retryable: **no** · idem: **no**

No such resource. IDs are account-scoped; check the ID and prefix.

### `resource_conflict`

<a id="resource_conflict"></a>**HTTP 409** · retryable: **no** · idem: **no**

Invalid state transition. GET the resource for its current state.

### `idempotency_key_reused`

<a id="idempotency_key_reused"></a>**HTTP 409** · retryable: **no** · idem: **no**

Idempotency-Key was reused with a different request body.

### `idempotency_unavailable`

<a id="idempotency_unavailable"></a>**HTTP 409** · retryable: **yes** · idem: **yes**

Idempotency store unavailable. Retry with the same key after Retry-After.

### `stream_already_active`

<a id="stream_already_active"></a>**HTTP 409** · retryable: **no** · idem: **no**

An active stream already exists on this account. End it (POST /v1/streams/{id}/end) or wait for it to finish.

### `upload_incomplete`

<a id="upload_incomplete"></a>**HTTP 400** · retryable: **no** · idem: **no**

Upload incomplete or size mismatch. Re-create the video and complete within 24h.

### `video_unreadable`

<a id="video_unreadable"></a>**HTTP 400** · retryable: **no** · idem: **no**

Container/codec unsupported. Re-encode to H.264 MP4.

### `video_too_large`

<a id="video_too_large"></a>**HTTP 400** · retryable: **no** · idem: **no**

Video exceeds the 2 GiB limit.

### `video_too_long`

<a id="video_too_long"></a>**HTTP 400** · retryable: **no** · idem: **no**

Video exceeds the plan duration limit. Upgrade or trim.

### `url_fetch_failed`

<a id="url_fetch_failed"></a>**HTTP 400** · retryable: **yes** · idem: **no**

Source URL unreachable or timed out.

### `url_forbidden`

<a id="url_forbidden"></a>**HTTP 400** · retryable: **no** · idem: **no**

URL scheme or host blocked by the ingest policy.

### `prompt_empty`

<a id="prompt_empty"></a>**HTTP 400** · retryable: **no** · idem: **no**

Provide prompt or query.

### `prompt_too_long`

<a id="prompt_too_long"></a>**HTTP 400** · retryable: **no** · idem: **no**

Prompt exceeds 2000 characters.

### `query_invalid`

<a id="query_invalid"></a>**HTTP 400** · retryable: **no** · idem: **no**

Body fails the CompiledQuery schema; param names the field.

### `parse_failed`

<a id="parse_failed"></a>**HTTP 422** · retryable: **yes** · idem: **no**

The compiler could not produce a query. Rephrase or send a structured query.

### `model_not_found`

<a id="model_not_found"></a>**HTTP 400** · retryable: **no** · idem: **no**

Unknown model. See GET /v1/models.

### `model_deprecated`

<a id="model_deprecated"></a>**HTTP 400** · retryable: **no** · idem: **no**

Model is deprecated. Pin a supported version; see the changelog.

### `analysis_not_cancelable`

<a id="analysis_not_cancelable"></a>**HTTP 409** · retryable: **no** · idem: **no**

Analysis is already in a terminal state.

### `rerun_not_eligible`

<a id="rerun_not_eligible"></a>**HTTP 409** · retryable: **no** · idem: **no**

This analysis is not eligible for a free re-run. Eligible: failed during a platform incident (rerun_eligible: true on the failed analysis) and not already re-run.

### `rate_limit_exceeded`

<a id="rate_limit_exceeded"></a>**HTTP 429** · retryable: **yes** · idem: **yes**

Rate limit exceeded. Back off per Retry-After.

### `concurrency_limit_exceeded`

<a id="concurrency_limit_exceeded"></a>**HTTP 429** · retryable: **yes** · idem: **yes**

Concurrent analysis limit reached. Wait for an active analysis to finish.

### `quota_exceeded`

<a id="quota_exceeded"></a>**HTTP 429** · retryable: **no** · idem: **no**

Quota exceeded for a metered limit. See details.meter.

### `insufficient_credits`

<a id="insufficient_credits"></a>**HTTP 402** · retryable: **no** · idem: **no**

Insufficient credits. Add credits or enable auto-refill.

### `grant_exhausted`

<a id="grant_exhausted"></a>**HTTP 402** · retryable: **no** · idem: **no**

Free grant exhausted. Claim a full account via POST /v1/keys/request to continue.

### `capacity_exhausted`

<a id="capacity_exhausted"></a>**HTTP 503** · retryable: **yes** · idem: **yes**

Platform queue is full. Honor Retry-After.

### `sandbox_limit_exceeded`

<a id="sandbox_limit_exceeded"></a>**HTTP 429** · retryable: **yes** · idem: **yes**

Sandbox provisioning limit reached for this IP. Retry after the window resets, or sign up for a live key.

### `upgrade_limit_exceeded`

<a id="upgrade_limit_exceeded"></a>**HTTP 429** · retryable: **no** · idem: **no**

Too many upgrade attempts from this IP. Retry after the window resets.

### `github_verification_required`

<a id="github_verification_required"></a>**HTTP 403** · retryable: **no** · idem: **no**

A verified GitHub account is required for the free grant. Complete the GitHub device flow and retry with github_token.

### `github_token_invalid`

<a id="github_token_invalid"></a>**HTTP 401** · retryable: **no** · idem: **no**

The github_token could not be verified with GitHub. Complete the device flow again and retry.

### `github_account_already_used`

<a id="github_account_already_used"></a>**HTTP 409** · retryable: **no** · idem: **no**

This GitHub account has already claimed a free grant. One free grant per GitHub account, ever.

### `device_code_expired`

<a id="device_code_expired"></a>**HTTP 410** · retryable: **no** · idem: **no**

Device code has expired or been consumed. Start a new request.

### `inference_unavailable`

<a id="inference_unavailable"></a>**HTTP 503** · retryable: **yes** · idem: **yes**

Transient inference backend outage. Retry with backoff.

### `upstream_error`

<a id="upstream_error"></a>**HTTP 502** · retryable: **no** · idem: **no**

Inference upstream returned an error. Message is sanitized — no internal paths or stack traces.

### `db_unavailable`

<a id="db_unavailable"></a>**HTTP 503** · retryable: **yes** · idem: **yes**

Transient database outage. Retry with backoff.

### `internal_error`

<a id="internal_error"></a>**HTTP 500** · retryable: **yes** · idem: **yes**

Internal error. Includes request_id; contact support if persistent.

### `webhook_endpoint_limit`

<a id="webhook_endpoint_limit"></a>**HTTP 400** · retryable: **no** · idem: **no**

At most 10 webhook endpoints per account.

## Resource-level errors

These never appear as HTTP responses — only in `analysis.error.code` / `video.error.code` on failed resources.

### `inference_error`

<a id="inference_error"></a>Model failed on this input. Safe to create a new analysis; persistent failures on one video → support with request_id.

### `stuck_timeout`

<a id="stuck_timeout"></a>The platform lost the job past the sweep window. Not billed; resubmit.

