# Errors

The error envelope, every error code and when to retry.

Source: https://scifig.ai/docs/api/errors

## Envelope

```json
{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Not enough credits.",
    "request_id": "a698a9af-0685-4557-9b29-ca57637748f9",
    "details": { "top_up_url": "https://scifig.ai/pricing?utm_source=api" }
  }
}
```

Branch on `error.code` and `details.reason`, never on `message`, which may change. Responses never contain internal diagnostics.

## Codes

| Code | HTTP | Meaning | Retry |
| --- | --- | --- | --- |
| UNAUTHORIZED | 401 | Missing or invalid credentials. | No; change the request or use a new key |
| ACCOUNT_UNAVAILABLE | 401 | This account cannot use the API. | No; change the request or use a new key |
| SCOPE_DENIED | 403 | The credential does not grant this permission. | No; change the request or use a new key |
| INVALID_REQUEST | 400 | The request is invalid. | No; change the request or use a new key |
| IDEMPOTENCY_KEY_REQUIRED | 400 | An Idempotency-Key header (1-255 visible ASCII characters) is required. | No; change the request or use a new key |
| IDEMPOTENCY_CONFLICT | 409 | This Idempotency-Key was already used with a different request. | No; change the request or use a new key |
| IDEMPOTENCY_IN_PROGRESS | 409 | A request with this Idempotency-Key is still being processed. | Yes, with the same Idempotency-Key |
| NOT_FOUND | 404 | Not found. | No; change the request or use a new key |
| REVISION_CONFLICT | 409 | The chart or flowchart job cannot be edited now: `NOT_LATEST_REVISION`, `EDIT_IN_PROGRESS`, or `PARENT_JOB_RUNNING` while the parent job is still running. | Only `PARENT_JOB_RUNNING`: after `Retry-After`, with the same key. Otherwise edit the latest version with a new key |
| PAYLOAD_TOO_LARGE | 413 | The request body is too large. | No; change the request or use a new key |
| INSUFFICIENT_CREDITS | 402 | Not enough credits. | No; change the request or use a new key |
| CONTENT_REJECTED | 422 | The content policy refused the request. Returned as `422` when the request is created, or as `error.code` of a failed job when it happens while the task runs; refunded either way. | No; change the prompt or the input and use a new key |
| PLAN_REQUIRED | 403 | Your plan does not include this option. | No; change the request or use a new key |
| RATE_LIMITED | 429 | Too many requests, tasks per hour, conversions per day or uploads; `details.reason` says which. | Yes, with the same Idempotency-Key |
| CONCURRENCY_LIMITED | 429 | Too many tasks are running for this account. | Yes, with the same Idempotency-Key |
| SERVICE_UNAVAILABLE | 503 | The service is temporarily unavailable. Retry with the same Idempotency-Key. | Yes, with the same Idempotency-Key |
| TASK_FAILED | 502 | The task failed and its credits were refunded. Send a new request (with a new Idempotency-Key) to try again. | No; change the request or use a new key |
| INTERNAL_ERROR | 500 | Internal error. | Yes, with the same Idempotency-Key |

`INVALID_REQUEST` always has `details.reason` (and `details.field` or `details.issues`); bodies larger than the limit (256 KB for JSON, the file limits for uploads) return `413 PAYLOAD_TOO_LARGE`. Only `INSUFFICIENT_CREDITS` carries `details.top_up_url`. When a create request fails after its job was created (`429`, `503`, `409 IDEMPOTENCY_IN_PROGRESS` or `500 INTERNAL_ERROR`), `details.job_id` names that job: retry with the same Idempotency-Key or poll `GET /jobs/{job_id}`, never a new key.

## Codes inside a failed job

A job that was created and then failed reports `status: "failed"` and an `error` object. Besides the codes above (for a refused request), its `error.code` can be:

| error.code | Meaning | What to do |
| --- | --- | --- |
| TASK_TIMED_OUT | The task timed out and was refunded. An illustration generation or edit may carry `late_delivery_until`: until then the result can still arrive for free. | Keep reading the job until `late_delivery_until`; otherwise send a new request with a new key |
| CONTENT_REJECTED | The content policy refused the result while the task ran; refunded. The same code is returned as `422` when the request itself is refused at creation. | Change the prompt or the input and use a new key |
| ADMISSION_INCOMPLETE | The job was never started because a retryable error stopped it. No credits were used. | Send the same request with the same Idempotency-Key |
| TASK_NOT_FOUND | The job was admitted but its task can no longer be found. | Send a new request with a new key |
| TASK_FAILED | The task failed and was refunded. Every engine failure without a public code of its own becomes `TASK_FAILED`; the engine code is in `details.reason`. | Send a new request with a new key |
| INTERNAL_ERROR | The task failed without reporting a reason; refunded. | Send a new request with a new key |

## Causes of 429

- Too many requests: 20 create requests per minute per credential, 40 per account, 120 reads per minute per credential. `Retry-After` is the time until the window resets; there is no `details.reason`.
- Too many tasks started in the last hour: up to 200 per account, shared with the website. `Retry-After: 30`.
- Too many tasks running: `429 CONCURRENCY_LIMITED`, by plan and shared with the website (see `GET /credits`). `Retry-After: 30`.
- The daily allowance of 200 free PNG, PDF, TIFF and PPTX conversions is used up: `details.reason = DAILY_CONVERSION_LIMIT`, `Retry-After` until the day window resets.
- Uploads too fast: `details.reason = UPLOAD_RATE_LIMITED` (images and masks on `POST /files`, flowchart attachments, chart data), the website’s per-minute upload limit.
- Too many uploads in a day: `details.reason = UPLOAD_DAILY_LIMIT_REACHED` (flowchart attachments and chart data), the website’s daily upload limit.
- Text detections: 20 per 15 minutes per account, shared with the website.
