# Errors and retries

Every error returns the same envelope. Branch on `error.code`, never on `message` — messages change,
codes are part of the compatibility contract.

```json
{ "error": { "type": "invalid_request_error", "code": "parameter_missing",
             "message": "Missing required parameter: query.", "param": "query",
             "doc_url": "https://docs.scispace.com/api-reference/searches",
             "request_id": "req_2f9a7c41d0" } }
```

## Error types

`error.type` is an exhaustive enum.

| HTTP | `type` | Retry? | Meaning |
|---|---|---|---|
| 400 | `invalid_request_error` | no | Malformed, missing, or out-of-range parameters |
| 401 | `authentication_error` | no | Missing, malformed, or revoked key |
| 402 | `insufficient_credits_error` | no | Balance or per-key cap exhausted |
| 403 | `permission_error` | no | Key lacks the required scope, or content is not licensed to you |
| 404 | `not_found_error` | no | Unknown, expired, or deleted resource |
| 409 | `conflict_error` | no | Resource is in the wrong state for this call |
| 413 | `payload_too_large_error` | no | Input or source count over the cap |
| 422 | `unprocessable_error` | no | Well-formed but unusable — unparseable PDF, unsupported language |
| 429 | `rate_limit_error` | yes, after `Retry-After` | Too many requests |
| 500 | `api_error` | yes, with backoff | Our fault |
| 503 | `service_unavailable_error` | yes, with backoff | Temporary capacity |

## Common codes

| `code` | Type | Where |
|---|---|---|
| `parameter_missing`, `parameter_invalid`, `parameter_unknown` | 400 | everywhere |
| `query_too_long` | 400 | searches — 1,000 character cap |
| `no_sources` | 400 | chats, extractions |
| `schema_invalid` | 400 | extractions |
| `csl_invalid` | 400 | citations |
| `unsupported_file_type`, `file_too_large` | 400 | files |
| `insufficient_credits` | 402 | any metered call |
| `scope_missing` | 403 | any |
| `full_text_not_licensed` | 403 | papers |
| `paper_not_found`, `resource_expired` | 404 | papers, async resources after 90 days |
| `document_not_ready` | 409 | chats, extractions |
| `search_not_cancelable` | 409 | searches |
| `too_many_sources` | 413 | chats (50), extractions (500) |
| `text_too_long` | 413 | paraphrases (5,000 words), ai-detections (15,000 words) |
| `document_unparseable` | 422 | documents |
| `language_unsupported` | 422 | any `language` parameter — see chats |
| `rate_limit_exceeded` | 429 | any |

## Retry strategy

Retry only `429`, `500`, and `503`.

```
attempt 1 → wait 1s ± jitter
attempt 2 → wait 2s ± jitter
attempt 3 → wait 4s ± jitter
attempt 4 → wait 8s ± jitter
attempt 5 → give up, surface the request_id
```

On `429`, honour `Retry-After` instead of your own schedule. Always send `Idempotency-Key` so a retry
cannot be charged twice — a replay within 24 hours returns the original response.

## Failed jobs are not transport errors

An async resource that reaches `status: "failed"` returns `200` with an `error` object on the resource.
Read it and fix the input; retrying an identical failed job costs credits and fails again.

## Getting help

Every response carries `X-Request-Id`. Quote it — see get-support.

## Related

conventions · rate-limits · job · troubleshooting
