> ## Documentation Index
> Fetch the complete documentation index at: https://docs.markpdf.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Common status codes and how to resolve them.

# Errors

The API returns errors as JSON with a `detail` field (or `error`, depending on the endpoint):

```json theme={null}
{
  "detail": "Missing url."
}
```

Error messages never include internal server paths, full signed URLs, or credentials. See [Security](/docs/security) for details on what is and is not filtered in `413`/`500` responses.

## Codes

| Status | Cause                                                     | Solution                                                                                                                                          |
| -----: | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|  `202` | Saturated backends; queued request.                       | Poll `GET /jobs/{job_id}` every 5s until `completed`. See [Jobs](/docs/api/jobs).                                                                 |
|  `304` | Not modified (matches `ETag` with `If-None-Match`).       | Not an error: use the content you already had in cache.                                                                                           |
|  `400` | Invavalid body or malformed URL.                          | Review the document and the `url`.                                                                                                                |
|  `401` | Missing or invavalid API key.                             | Send a vavalid `x-api-key`. See [Authentication](/docs/authentication).                                                                           |
|  `403` | Unauthorized access or URL host not allowed.              | Check your key and the original URL. In `/convert/from-url`, if you use `ALLOWED_FETCH_HOSTS`, confirm that the storage host is in the allowlist. |
|  `413` | Document too large, too many pages, or ZIP out of bounds. | Reduce the size or split the document. See [Heavy files and limits](/docs/api/heavy-and-limits).                                                  |
|  `415` | Unsupported format or `Content-Encoding`.                 | Use a supported format; compress only with gzip or zstd.                                                                                          |
|  `422` | Missing parameters.                                       | Check `url`, `filename`, and `input_format`.                                                                                                      |
|  `429` | Too many requests.                                        | Apply backoff and retry.                                                                                                                          |
|  `500` | Conversion error.                                         | Try another `mode` or verify that the document is not corrupted.                                                                                  |

## Common edge cases

* **Empty file (0 bytes)**: responds `400` because there is no content to convert.
* **Unrecognized format** (unsupported extension and generic `content-type`): responds `415`. Force `input_format` explicitly if you know the real format and the filename does not make it clear.
* **Corrupt PDF or invavalid header**: usually `400` during early vavalidation, or `500` if extraction fails. Try `mode=quality` before discarding the document.
* **Invavalid or unreachable URL on `/convert/from-url`**: `400` if the URL format is invavalid, `403` if the host is not allowed, `502`/`504` if the remote storage does not respond or drops the connection mid-download.
* **Conversion timeout**: for very large documents or many images, the API may take longer than your HTTP client expects by default. Set a generous timeout (300s or more) on clients like `httpx`/`fetch`; if the system is saturated you will receive `202` instead of blocking the connection. See [Jobs](/docs/api/jobs).
* **ZIP without any supported document inside, or with more files than `MAX_ZIP_FILES`**: `413` or `415` depending on the case; the message indicates which limit was violated.

## Retries

* `429` and `5xx`: retry with exponential backoff (for example 1s, 2s, 4s, with jitter).
* `4xx` (except `429`): do not retry without correcting the request. The error will not disappear by repeating the same call.
* `202`: not an error. It is the signal that you should start polling `GET /jobs/{id}`.

```python theme={null}
import httpx, time

def convert_with_retry(url, headers, json_body, max_retries=3):
    for attempt in range(max_retries):
        r = httpx.post(url, headers=headers, json=json_body, timeout=300)
        if r.status_code == 200:
            return r.text
        if r.status_code == 202:
            return None  # manejar con polling a /jobs/{id}
        if r.status_code in (429, 500, 502, 503, 504):
            time.sleep(2 ** attempt)
            continue
        r.raise_for_status()  # 4xx no reintentable
    raise RuntimeError("Maximum retries reached")
```

```javascript theme={null}
async function convertWithRetry(url, headers, body, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const res = await fetch(url, { method: "POST", headers, body });
    if (res.status === 200) return res.text();
    if (res.status === 202) return null; // handle by polling /jobs/{id}
    if ([429, 500, 502, 503, 504].includes(res.status)) {
      await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
      continue;
    }
    throw new Error(`Non-retryable error: ${res.status}`);
  }
  throw new Error("Maximum retries reached");
}
```

<Tip />

<Note>
  The codes `400`/`403`/`413`/`415`/`422` are client errors: the request will not succeed by repeating it as is. Correct the parameter, URL, or format before retrying.
</Note>
