---
seo_title: API Error Codes — 400, 401, 402, 403, 413 Explained | EnConvert
meta_desc: Every HTTP status code the Enconvert API returns — 401 invalid API key, 402 conversion limit reached, 413 file too large — with exact error messages and fixes.
keywords: api 402 payment required conversion limit, 401 unauthorized x-api-key, 413 payload too large file upload, enconvert api error codes, api 403 forbidden plan upgrade fix, file conversion api error response format, jwt token expired 401 api, 503 converter not available retry
---

# Enconvert API Error Codes

This reference lists every HTTP status code and error message the Enconvert API returns, from `200 OK` for synchronous conversions and `202 Accepted` for async and batch jobs to the error responses documented below. Each error section lists the exact message strings, the condition that triggers each one, and how to fix the request. Most errors return a JSON body with a `detail` field; `413 Payload Too Large` uses a structured format described in its own section.

---

## HTTP Status Codes

| Code | Status | Description |
|------|--------|-------------|
| `200` | OK | Conversion completed successfully (sync mode). |
| `202` | Accepted | Batch or async job has been accepted for background processing. |
| `400` | Bad Request | Invalid parameters, missing required fields, malformed request body, or invalid file content. |
| `401` | Unauthorized | Missing, invalid, or expired API key or JWT token. |
| `402` | Payment Required | Monthly conversion limit or storage limit exceeded. |
| `403` | Forbidden | Feature not available on plan, domain not authorized, endpoint not allowed, or access denied. |
| `404` | Not Found | Requested resource (job, batch, file, or widget) does not exist. |
| `413` | Payload Too Large | Uploaded file exceeds the size limit for your subscription plan. |
| `415` | Unsupported Media Type | The target URL returned content this converter cannot render (e.g. JSON to `url-to-pdf`). |
| `422` | Unprocessable Entity | A render precondition failed — e.g. a `wait_for_selector` that never appeared — or a V2 validation error. |
| `500` | Internal Server Error | Unexpected error during conversion (our engine faulted). |
| `502` | Bad Gateway | The target site could not be reached, or an upstream provider failed. |
| `503` | Service Unavailable | A converter or backend service is unavailable, or the render pool is at capacity. |
| `504` | Gateway Timeout | The target site took too long to respond or finish loading. |

---

## Error Response Format

Most error responses follow a standard JSON structure with a `detail` field:

```json
{
    "detail": "Error message describing the issue"
}
```

<div class="alert alert-info">
<strong>Exception:</strong> The <code>413 Payload Too Large</code> response uses a different format with structured fields instead of <code>detail</code>. See the <a href="#413-payload-too-large">413 section</a> below.
</div>

---

## 400 Bad Request

Returned when the request contains invalid parameters, missing fields, or malformed data.

### Input Validation

| Message | Condition |
|---------|-----------|
| `'url' must be provided` | Missing or empty `url` field on URL-based endpoints. |
| `Invalid file format '{ext}' for {endpoint}. Allowed: {list}` | Uploaded file extension doesn't match the endpoint's accepted formats. |
| `Invalid pdf_options: {error}` | Malformed JSON in the `pdf_options` form field. |

### Batch and Mode Validation

| Message | Condition |
|---------|-----------|
| `Public keys only support a single URL input` | Public/dashboard key attempted to send multiple URLs. |
| `output_format=True requires multiple URLs` | ZIP bundling requested with only one URL. |
| `direct_download not supported for multiple URLs` | `direct_download=true` with an array of URLs. |
| `direct_download only works in sync mode` | `direct_download=true` combined with `async_mode=true`. |

### Auth, Cookies & Headers Validation

| Message | Condition |
|---------|-----------|
| `'auth' must be an object with 'username' and 'password'` | `auth` parameter has wrong structure. |
| `'cookies' must be an array of cookie objects` | `cookies` is not an array. |
| `'cookies' array must not exceed 50 entries` | More than 50 cookies provided. |
| `Cookie at index {i} must be an object` | Cookie entry is not a dictionary. |
| `Cookie at index {i} must have 'name' and 'value'` | Cookie missing required fields. |
| `Cookie at index {i} must have 'domain' or 'url'` | Cookie missing both `domain` and `url`. |
| `'headers' must be an object of header name/value pairs` | `headers` is not a dictionary. |
| `'headers' must not exceed 20 entries` | More than 20 custom headers. |
| `Header '{name}' cannot be overridden` | Attempt to set a blocked header (`host`, `content-length`, etc.). |
| `Header '{name}' value must be a string` | Header value is not a string. |
| `Cannot use both 'auth' and an 'Authorization' header` | Both `auth` object and `Authorization` custom header provided. |

### URL Safety (SSRF)

Every URL-based endpoint screens the target `url` before fetching it. These messages are returned as `400` when the URL is not a public `http(s)` address.

| Message | Condition |
|---------|-----------|
| `Only http:// and https:// URLs are supported.` | The URL uses a scheme other than `http` or `https`. |
| `URLs with embedded credentials are not allowed. Use the 'auth' field for HTTP Basic Auth.` | The URL embeds a username/password (`https://user:pass@host/`). |
| `URL has no hostname.` | The URL could not be parsed into a host. |
| `This hostname is not allowed.` | The host is `localhost` or a cloud metadata hostname. |
| `URLs resolving to private or internal addresses are not allowed.` | The URL is, or resolves to, a private, loopback, link-local, reserved, or otherwise non-public IP. |
| `Non-standard IP address notation is not allowed.` | The host uses octal, hexadecimal, or packed-integer IP notation that could resolve ambiguously. |
| `Could not resolve hostname '{hostname}'.` | DNS resolution for the host failed. |

### Render Option Validation

| Message | Condition |
|---------|-----------|
| `'wait_for_selector' must be a string` | `wait_for_selector` was not a string. |
| `'wait_for_selector' is too long (max 1000 chars)` | Selector exceeds 1000 characters. |
| `'wait_for_selector_timeout' must be a positive integer (ms)` | Timeout is missing, zero, negative, or not an integer. |
| `'wait_for_selector_timeout' must not exceed 60000 ms` | Timeout above the 60-second ceiling. |
| `'block_ads' must be a boolean` / `'block_media' must be a boolean` | Blocking flag was not a boolean. |

### Sitemap and Crawl Errors

| Message | Condition |
|---------|-----------|
| `No URLs found in sitemap: {url}` | Sitemap parsed but contains no URLs. |
| `Timeout fetching sitemap: {url}` | Sitemap fetch exceeded 30-second timeout. |
| `Could not fetch sitemap: {url} returned {status}` | Sitemap URL returned a non-200 HTTP status. |
| `Invalid XML in sitemap: {url}` | Sitemap XML could not be parsed. |
| `Unrecognized sitemap format at {url}: root element is <{tag}>` | Sitemap root element is not `<urlset>` or `<sitemapindex>`. |
| `No pages discovered on {base_url}` | Full crawl completed but found zero pages. |

### Conversion Content Errors

| Message | Condition |
|---------|-----------|
| `Invalid JSON: {error}` | JSON file contains invalid JSON syntax. |
| `Invalid YAML: {error}` | YAML file contains invalid YAML syntax. |
| `Invalid TOML: {error}` | TOML file contains invalid TOML syntax. |
| `Invalid HTML encoding (expected UTF-8)` | HTML file is not UTF-8 encoded. |
| `Invalid Markdown encoding (expected UTF-8)` | Markdown file is not UTF-8 encoded. |
| `JSON must be an array of objects for CSV conversion` | json-to-csv input is not an array. |
| `JSON array is empty` | json-to-csv input is an empty array. |
| `CSV file is empty or has no valid rows` | CSV file has no data rows. |
| `XML structure cannot be converted to CSV` | XML is not tabular (xml-to-csv). |
| `Turnstile verification failed` | Cloudflare Turnstile bot challenge failed. |
| `Turnstile token required` | Widget request without a Turnstile token. |

---

## 401 Unauthorized

Returned when authentication is missing or invalid.

| Message | Condition |
|---------|-----------|
| `Authentication required` | No API key and no JWT token provided in the request. |
| `Invalid API Key format` | API key is too short or does not start with `sk_` or `pk_`. |
| `Invalid API Key` | API key hash not found in the database. |
| `API Key revoked` | API key has been deactivated from the dashboard. |
| `Token has expired` | JWT access token has expired (1-hour lifetime). |
| `Invalid token` | JWT is malformed, tampered with, or otherwise invalid. |
| `Refresh token has expired` | Refresh token has expired (7-day lifetime). |
| `Invalid refresh token` | Refresh token is malformed or invalid. |
| `Invalid token type` | Token decoded successfully but is not the expected type (refresh). |
| `No refresh token` | Widget refresh endpoint called without a refresh_token cookie. |

---

## 402 Payment Required

Returned when usage limits are exceeded.

| Message | Condition |
|---------|-----------|
| `Monthly conversion limit reached ({used}/{limit}). Upgrade your plan to continue.` | Monthly conversion count has reached the plan's limit. Free plan: 100 conversions. |
| `Batch of {N} URLs would exceed your monthly limit. You have {remaining} conversions remaining out of {limit}.` | Batch request would exceed the remaining monthly quota. The entire batch is rejected upfront. |
| `Storage limit reached. Delete files or upgrade your storage plan to continue.` | Project storage usage has reached the plan's storage allocation. |

---

## 403 Forbidden

Returned when access is denied due to key type, domain, plan, or endpoint restrictions.

### API Key and Token Restrictions

| Message | Condition |
|---------|-----------|
| `Private API keys cannot be used from browsers` | A private key (`sk_live_...`) was used in a request with a browser `Origin` header. Use a public key with JWT instead. |
| `Domain {origin} not authorized` | Request origin does not match any domain in the API key's allowed domains list. |
| `Public API keys can only be used to generate JWT tokens. Please exchange your public key for a JWT token at /v1/auth/token, then use the token for API calls.` | A public key was used to call a conversion endpoint directly instead of exchanging for a JWT first. |
| `Endpoint '{path}' not allowed for this API key` | The API key's `allowed_endpoints` list does not include the requested path. |
| `Endpoint '{path}' not allowed for this token` | The JWT token's `allowed_endpoints` list does not include the requested path. |
| `Token issued for different origin` | Request origin does not match the origin recorded in the JWT (prevents token theft). |
| `Parent origin does not match token` | `X-Parent-Origin` header does not match what was validated at token issuance. |

### Plan Feature Restrictions

| Message | Condition |
|---------|-----------|
| `Async processing is not available on your current plan. Please upgrade to access this feature.` | `async_mode=true` on a plan without async access. |
| `Webhook callbacks is not available on your current plan. Please upgrade to access this feature.` | `callback_url` provided on a plan without webhook access. |
| `ZIP output bundling is not available on your current plan. Please upgrade to access this feature.` | `output_format=true` on a plan without ZIP output access. |
| `HTTP Basic Auth, cookies, and custom headers is not available on your current plan. Please upgrade to access this feature.` | `auth`, `cookies`, or `headers` used on a plan without basic auth access. |
| `Batch processing is not available on your current plan. Please upgrade to access this feature.` | Multiple URLs submitted on a plan with batch_limit of 0. |
| `Batch size {N} exceeds your plan's limit of {M} URLs per batch.` | Number of URLs exceeds the plan's batch size limit. |
| `Website crawling is not available on your current plan. Please upgrade to access this feature.` | Website capture endpoint used on a plan with crawl_mode "none" (Free plan). |
| `Full website crawling requires a Pro plan or higher. Your plan supports sitemap-based crawling only.` | `crawl_mode=full` requested on a Starter plan that only supports sitemap crawling. |

### Widget Restrictions

| Message | Condition |
|---------|-----------|
| `Widget API key has been revoked` | The widget's linked internal API key has been deactivated. |
| `Domain {origin} is not authorized for this widget` | Widget embedding domain not in the widget's allowed domains list. |
| `Refresh token does not match widget` | Refresh token's project ID does not match the widget's project. |
| `Batch status requires a private API key` | Public or dashboard key attempted to access `GET /v1/convert/batch/{batch_id}`. |
| `Access denied` | Attempting to access a resource (job status, file) belonging to a different project. |

---

## 404 Not Found

| Message | Condition |
|---------|-----------|
| `Job not found` | Conversion job ID not found in the database (status polling). |
| `Batch not found` | Batch ID has no matching activity rows for this project. |
| `File not found` | Requested file does not exist in storage (download endpoint). |
| `Widget not found` | Widget ID not found or widget has been deactivated. |

---

## 413 Payload Too Large

Returned when the uploaded file exceeds the plan's maximum file size.

<div class="alert alert-warning">
<strong>Different response format:</strong> This error uses a structured object instead of the standard <code>detail</code> field.
</div>

```json
{
    "error": "File too large",
    "file_size": 10485760,
    "max_size": 5242880,
    "tier": "free",
    "key_type": "private"
}
```

| Field | Description |
|-------|-------------|
| `error` | Always `"File too large"`. |
| `file_size` | The size of the uploaded file in bytes. |
| `max_size` | The maximum allowed file size for your plan in bytes. |
| `tier` | Your subscription plan slug (e.g., `"free"`, `"starter"`, `"pro"`). |
| `key_type` | The type of API key used (`"private"`, `"public"`). |

File size is checked via the `Content-Length` request header before the file body is read.

---

## Browser Conversion Errors (415 / 422 / 502 / 504)

URL conversions distinguish a fault in the **target site or the input** (a `4xx`, `502`, or `504` you can act on) from a fault in **our engine** (a `500`). These typed failures use a structured body instead of the plain `detail` field:

```json
{
    "error": "Gateway Timeout",
    "code": "upstream_timeout",
    "detail": "The target site took too long to respond while rendering PDF for https://example.com."
}
```

| Code | `code` field | Condition |
|------|--------------|-----------|
| `415` | `unsupported_content_type` | The target returned content the converter cannot render — e.g. `application/json` to `url-to-pdf` or `url-to-screenshot`. Use `url-to-markdown` for JSON. |
| `422` | `selector_not_found` | A caller-supplied `wait_for_selector` never appeared within `wait_for_selector_timeout`. |
| `502` | `upstream_unreachable` | The target site could not be reached (DNS or connection failure). |
| `502` | `empty_render` | Navigation finished but the page produced no capturable content. |
| `504` | `upstream_timeout` | The target site took too long to respond or finish loading. |

<div class="alert alert-info">
A `500` now means our engine faulted — retrying an identical request is unlikely to help. A `502`/`504` means the <em>target</em> misbehaved: retry, or check the URL.
</div>

---

## 500 Internal Server Error

| Message | Condition |
|---------|-----------|
| `Conversion failed: {error}` | An unexpected error occurred during file or URL conversion. |

If you encounter persistent 500 errors, the issue is likely with the input file or URL. Try with a different input to isolate the problem.

---

## 503 Service Unavailable

| Message | Condition |
|---------|-----------|
| `Converter not available: {endpoint}` | The requested converter is not registered or not running. |
| `Converter not available` | The URL-based converter for the requested endpoint is not available. |
| `The conversion service is at capacity. Please retry shortly.` | The render pool is saturated. The response includes a `Retry-After` header. |
| `Turnstile verification unavailable` | The Cloudflare Turnstile verification service is unreachable. |

These errors are typically transient. Retry the request after a short delay.

---

## V2 endpoint errors

The [V2 web intelligence endpoints](/docs/v2-overview) reuse the status codes above, with a few V2-specific conditions worth calling out.

### Quota and plan (402 / 403)

V2 operations meter against separate counters from V1 conversions, and each is a hard limit with no overage.

| Code | Condition |
|------|-----------|
| `402` | A V2 quota is exhausted: `perceive_operations` ([perceive](/docs/v2-perceive)), `lookup_queries` ([lookup](/docs/v2-lookup)), `distill_operations` ([distill](/docs/v2-distill)), `ingest_pages` ([ingest](/docs/v2-ingest)), or the watcher limit ([watch](/docs/v2-watch)). V1 plans include no V2 quota. |
| `402` | The per-period LLM extraction budget is exhausted (schema extraction on perceive and distill falls back to the heuristic/CSS result rather than overspending). |
| `403` | The endpoint is not in the API key's `allowed_endpoints` allowlist, or the V2 feature is not on the current plan. |

### Validation (422)

| Endpoint | Condition |
|----------|-----------|
| [perceive](/docs/v2-perceive) | `proxy_url`, `geolocation`, or `action_chain` was sent — reserved for a later release. |
| [distill](/docs/v2-distill) | Output `schema` is missing or not an object; neither or both of `urls` and `discover_from` supplied; an invalid CSS field, an unsupported field type, or a regex that risks catastrophic backtracking. |
| [watch](/docs/v2-watch) | `frequency_minutes` below the 60-minute hourly floor; an empty `PATCH` body. |
| [ingest](/docs/v2-ingest) | The `mode` does not match the source (`urls` mode without `urls`, or `sitemap`/`crawl` without a seed `url`). |

### Search provider (502 / 503)

The [lookup](/docs/v2-lookup) endpoint depends on an upstream search provider. Raw provider error text never reaches the client.

| Code | Condition |
|------|-----------|
| `502` | The search provider returned an error response or a non-retryable transport fault. |
| `503` | The search provider is misconfigured (missing key) or temporarily unreachable — the circuit breaker is open, or the provider rate-limited us. Retry later. |

### Not found (404)

`GET` and `DELETE` on a V2 `operation_id`, `job_id`, or `watcher_id` that does not exist — or that belongs to a different project — returns `404`. Existence is never leaked across projects.

### Accepted (202)

[Ingest](/docs/v2-ingest) is always asynchronous: `POST /v2/ingest` answers `202` with a `job_id` you poll. Perceive batches larger than 10 URLs also answer `202`.

---

## Troubleshooting

### Authentication Issues

- **Getting 401?** Check that your API key is valid and active in the dashboard. If using JWT, ensure the token hasn't expired (1-hour lifetime).
- **Getting 403 about browser usage?** You're using a private key (`sk_live_...`) from client-side code. Switch to a public key with JWT for browser-based requests.
- **Getting 403 about domain?** Add your domain to the API key's allowed domains list in the dashboard.

### Conversion Issues

- **Getting 400 about file format?** Ensure the uploaded file extension matches the endpoint (e.g., `.json` for json-to-xml, `.docx` for doc-to-pdf).
- **Getting 413?** Your file exceeds the plan's size limit. Check your plan's max file size or upgrade.
- **Getting 402?** You've hit your monthly conversion limit or storage limit. Check usage in the dashboard.

### Feature Access Issues

- **Getting 403 about plan features?** The feature you're trying to use (async, batch, webhooks, etc.) requires a higher plan tier. Check the [feature gating table](/docs/parameters-options#feature-gating-summary).

## Frequently asked questions

### Why does the API return 402 Payment Required for a file conversion?

A `402` means a usage limit is exhausted — either your monthly conversion limit (100 conversions on the Free plan) or your project's storage allocation. Batch requests that would exceed the remaining monthly quota are rejected upfront with a `402` for the entire batch, and V2 endpoints meter against their own separate quotas with no overage.

### How do I fix a 401 Unauthorized error from the conversion API?

Check that the API key is present, starts with `sk_` or `pk_`, and is still active in the dashboard — revoked keys return `API Key revoked`. If you authenticate with a JWT, note that access tokens expire after 1 hour (`Token has expired`) and refresh tokens after 7 days.

### Why am I getting 413 Payload Too Large when uploading a file?

The uploaded file exceeds your plan's maximum file size, which is checked via the `Content-Length` request header before the file body is read. Unlike other errors, the `413` response is a structured object with `file_size`, `max_size` (both in bytes), `tier`, and `key_type` fields instead of the standard `detail` field.

### Can I use a private API key from browser JavaScript?

No — a private key (`sk_live_...`) used in a request with a browser `Origin` header returns `403 Private API keys cannot be used from browsers`. Exchange a public key for a JWT at `/v1/auth/token` and use that token for browser-based API calls.

### Is a 503 Service Unavailable error from the API permanent?

No, `503` errors such as `Converter not available: {endpoint}` or `Turnstile verification unavailable` are typically transient. Retry the request after a short delay.
