EnConvert API Error Codes#
This reference lists the HTTP status codes and error messages 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. Error bodies do not all have the same shape: there are six, and the Error response format section shows each one.
A job that fails after it was accepted is not an HTTP error. The 202 stands and the failure appears in the job's status payload when you poll it, described in Sync and async jobs.
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 ops allowance exhausted, no active billing period, watcher cap reached, storage limit reached, or a V2 endpoint switched off on your plan. |
403 |
Forbidden | Key type, domain, or endpoint-allowlist restriction, a V1 feature gate (async, webhooks, ZIP output, basic auth, batch), or access to another project's resource. |
404 |
Not Found | Requested resource (job, batch, operation, file, watcher, or widget) does not exist, or the path is not a route. |
405 |
Method Not Allowed | The path exists but not for the HTTP method you used. |
409 |
Conflict | A client-supplied job_id is already in use, or a webhook retry was asked for on an ingest job that has not completed. |
410 |
Gone | A V2 artifact or batch archive has passed your plan's file-retention window and is no longer in storage. |
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 | The request body failed schema validation (including unknown fields on V2 endpoints), or a render precondition failed such as a wait_for_selector that never appeared. |
429 |
Too Many Requests | A short-window request-rate limit was tripped. This is not the quota code; monthly allowance exhaustion answers 402. |
500 |
Internal Server Error | Unexpected error during conversion (our engine faulted). |
502 |
Bad Gateway | The target site could not be reached, an upstream provider failed, or the target served an anti-bot challenge with no page content (/v2/perceive, unless allow_degraded is set). |
503 |
Service Unavailable | A converter is not available, the render pool or the conversion admission gate is at capacity, or an upstream dependency is down. |
504 |
Gateway Timeout | The target site took too long to respond or finish loading, or the request exceeded the gateway's 300-second budget. |
Error Response Format#
Six body shapes exist. Which one you get depends on where the failure happened, not on the status code alone, so check the type of detail before you read it.
1. String detail. The common case, and the only shape most integrations see.
{
"detail": "Authentication required"
}
2. Object detail. The plan file-size 413. The structured object is the value of detail, so read body.detail.max_size, not body.max_size. See 413 Payload Too Large.
{
"detail": {
"error": "File too large",
"file_size": 10485760,
"max_size": 5242880,
"tier": "free",
"key_type": "private"
}
}
3. Array detail plus errors. Schema validation (422) returns both: detail is the raw validator output, errors is a parallel array of human-readable strings. See 422 Unprocessable Entity.
4. Typed conversion envelope. {"error", "code", "detail"}, with a machine-readable code. Emitted only by the three V1 URL conversion endpoints. See Browser conversion errors.
5. Unhandled exception. A 500 that did not come from a converter has no detail at all:
{
"error": "Internal server error",
"event_id": "a1b2c3d4"
}
6. Gateway request timeout. The gateway's own 300-second budget produces a 504 with no detail and no code:
{
"error": "Request timeout"
}
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. |
File content does not match the '{endpoint}' input type. |
The extension was accepted but the file's magic bytes are for a different format. |
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. The blocked set is host, content-length, transfer-encoding, connection, upgrade, te, trailer. |
Header '{name}' value must be a string |
Header value is not a string. |
Cannot use both 'auth' and an 'Authorization' header. Use 'auth' for HTTP Basic Auth or 'headers' for Bearer/custom auth, not both. |
Both an auth object and an Authorization custom header were 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. |
This URL is blocked by the site's threat policy. |
The target host is on the threat-policy denylist, checked alongside the SSRF screen. |
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. |
Refresh token not found |
The presented refresh token is not stored against any session. |
User not found or invalid |
The token decoded but its subject no longer resolves to a usable account. |
Project not found |
The project id on the key or token could not be parsed while checking the ops allowance. |
402 Payment Required#
Returned when a usage limit is exceeded. The split between 402 and 403 is not symmetric, and it catches people out. Every quota condition answers 402, and so does a V2 endpoint that is switched off on your plan. V1 feature gates answer 403 (async, webhooks, ZIP output, basic auth, batch). Rate limiting is a separate mechanism that answers 429, never 402.
| Message | Condition |
|---|---|
Monthly operations limit reached ({used}/{limit}). Upgrade your plan to continue. |
The unified monthly ops counter has reached the plan's allowance. Founding plan: 500 ops. Every endpoint draws from this one counter. On any paid plan with overage enabled, requests continue at $0.02/op instead of failing. |
This request needs {units} operations but only {remaining} of your {limit} monthly operations remain. Upgrade your plan to continue. |
Batch request would exceed the remaining monthly ops allowance. The entire batch is rejected upfront. |
No active billing period found for this project. Contact support to restore your subscription. |
The project has no usage period and none could be provisioned from its subscription. The gate fails closed rather than granting a free operation. |
Storage limit reached. Delete files or upgrade your storage plan to continue. |
Project storage usage has reached the plan's storage allocation. |
Active watcher limit reached ({active_count}/{limit}). Delete an existing watcher or upgrade your plan to add more. |
The project already holds its plan's maximum number of active watchers. Watchers consume no ops; this is a cap on how many exist at once. |
{Feature} is not available on your current plan. Upgrade to a V2-inclusive plan to access this endpoint. |
A V2 endpoint is disabled for the plan. |
Running out of monthly AI credits does not produce a 402. Schema extraction falls back to the heuristic and CSS result and the request still succeeds. Allowances, prices and what counts as one operation are in Rate limits and quotas.
403 Forbidden#
Returned when access is denied due to key type, domain, V1 plan feature, or endpoint restrictions. V2 endpoint gates are the exception: they answer 402, not 403.
API Key and Token Restrictions#
| Message | Condition |
|---|---|
Private API keys cannot be used from browsers |
A private key (sk_...) 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 or fetch widget branding. 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 on any path other than /auth/token or /auth/branding. Exchange it 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. |
Basic authentication, cookies & 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" (Founding plan). |
Full website crawling requires a Studio plan or higher. Your plan supports sitemap-based crawling only. |
crawl_mode=full requested on an Indie 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. |
Two more 403 messages are about neither keys nor plans: Account suspended, returned for every request once the account behind the key or token is suspended, and robots.txt disallows fetching this URL (request sent respect_robots=true)., returned by perceive when you asked for robots compliance and the target disallows the path.
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. |
Operation not found, Ingest job not found, Watcher not found |
A V2 resource id that does not exist, or belongs to another project. Existence is never leaked across projects. |
Not Found |
The path is not a route on the API. Check the path and the version prefix. |
409 Conflict#
| Message | Condition |
|---|---|
job_id already in use |
A client-supplied job_id is already claimed by another project. Pick a different id, or let the API generate one. |
A completion webhook is only delivered for completed jobs. |
A webhook retry was requested for an ingest job that has not reached completed. |
410 Gone#
The artifact existed but has passed your plan's file-retention window and is no longer in storage. Retention is per plan; see Rate limits and quotas.
| Message | Condition |
|---|---|
The artifact is no longer in storage (it may have passed your plan's file-retention window). Re-run the perceive request to regenerate it. |
Artifact download on GET /v2/perceive/{operation_id}. |
The batch archive is no longer in storage (it may have passed your plan's file-retention window). |
ZIP download on GET /v2/perceive/batch/{job_id}. |
Treat 410 as final for that object. Re-running the request produces a fresh artifact; retrying the download does not.
413 Payload Too Large#
Returned when the uploaded file exceeds the plan's maximum file size.
detail, not a top-level object. Read body.detail.max_size.
{
"detail": {
"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"), falling back to "free" when no plan is resolved. Slugs are stable API identifiers; the customer-facing names are Founding (free), Indie (starter), Studio (pro), and Production (business). |
key_type |
The type of API key used: "private", "public", or "unknown". |
The limit is checked against the uploaded part's exact byte count before any conversion work starts. A file whose size is exactly max_size is accepted; only a larger file is rejected. The Content-Length header is a fallback for older call sites that do not hand their upload object to the check.
POST /v2/ingest/files does not use this shape. It answers 413 with a plain string detail: File '{filename}' exceeds the {max_size}-byte limit.
Per-plan ceilings are listed in Rate limits and quotas, and the upload paths this applies to are in File ingestion.
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). The three V1 URL conversion endpoints (url-to-pdf, url-to-screenshot, url-to-markdown) return these typed failures with a machine-readable code alongside detail:
{
"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, for example application/json sent 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. |
Those five are the whole vocabulary. No other endpoint family emits a code, V2 included: a V2 failure comes back as a plain detail string. The envelope's base class defines a sixth slug, conversion_error, but nothing raises it, so it never reaches you. Branch on the five above and treat any other value as unknown.
A 504 can also arrive in two untyped shapes: {"error": "Request timeout"} when the request outlives the gateway's 300-second budget, and a plain string detail carrying the timeout message when a document conversion (LibreOffice) times out. Neither carries a code.
422 Unprocessable Entity#
Schema validation failures return two parallel arrays. detail is the raw validator output, which is what you map back to form fields. errors is one human-readable string per problem, which is what you show a user.
{
"detail": [
{
"loc": ["body", "max_pages"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"type": "int_parsing"
}
],
"errors": [
"body.max_pages: Input should be a valid integer, unable to parse string as an integer (you sent 'ten')"
]
}
Three type values are worth handling by name:
type |
Meaning |
|---|---|
extra_forbidden |
Unknown field. V2 request schemas reject unknown keys rather than ignoring them, so a misspelled parameter is a 422 that names the field instead of a silently dropped option. |
missing |
A required field was not sent. |
json_invalid |
The request body was not valid JSON. |
POST /v2/perceive/batch returns a 422 whose detail is a bare list of {"loc", "msg"} objects, with no type key and no top-level errors array. Parsers that assume errors is always present will break there.
A 422 with a selector_not_found code is a different thing: a render precondition that failed, covered in Browser conversion errors.
429 Too Many Requests#
Rate limiting is a short-window fairness control and is separate from the monthly ops allowance. Exhausting the allowance answers 402; only the rate limiter answers 429.
| Message | Condition |
|---|---|
Rate limit exceeded. Please slow down and retry shortly. |
A request-rate window for the project was exceeded. Buckets are per project and are namespaced by key type, so public and private traffic do not share one. |
A 429 carries four headers:
| Header | Meaning |
|---|---|
RateLimit-Limit |
Requests allowed in the window that tripped. |
RateLimit-Remaining |
Requests left in that window, 0 on a rejection. |
RateLimit-Reset |
Seconds until the window resets. |
Retry-After |
The same value as RateLimit-Reset. Wait this long before retrying. |
These headers appear on the 429 only. Successful responses carry no rate-limit headers and no remaining-ops headers, so you cannot read your remaining budget off a response; check usage in the dashboard. See Rate limits and quotas.
500 Internal Server Error#
| Message | Condition |
|---|---|
Conversion failed: {error} |
An unexpected error during a file upload conversion. URL conversions do not use this message: they surface as the typed envelope above, or as the generic body below. |
Perception failed. Reference operation_id '{id}' when contacting support. |
An unexpected fault inside a /v2/perceive run. The other V2 endpoints have equivalents, such as Distillation failed. Reference operation_id ... and Could not start ingest job. Reference job_id .... Quote the id when you contact support. |
Anything that faults outside a converter never reaches you as text. It comes back without a detail at all:
{
"error": "Internal server error",
"event_id": "a1b2c3d4"
}
One case surprises people: a malformed JSON body sent to a V1 URL endpoint (url-to-pdf, url-to-screenshot, url-to-markdown, website-to-pdf, website-to-screenshot) returns this 500 rather than a 422, because those endpoints read the raw body. The same malformed body on a V2 endpoint returns a 422 with type: json_invalid.
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 browser render pool has no free slot. Sent with Retry-After: 30. |
Server is at capacity. Please retry shortly. |
The CPU conversion admission gate is full: too many file conversions, or too many bytes, already in flight. Sent with Retry-After: 10. |
Search is temporarily unavailable. Please try again later. |
The upstream search provider behind lookup is unreachable or misconfigured. |
Turnstile verification unavailable |
The Cloudflare Turnstile verification service is unreachable. |
Two different capacity gates exist and they ask for different waits, so read Retry-After instead of assuming one. These errors are otherwise transient: retry after a short delay.
V2 endpoint errors#
The V2 web intelligence endpoints reuse the status codes above, with a few V2-specific conditions worth calling out.
Quota and plan (402 / 403)#
V2 operations meter against the same unified monthly ops allowance as V1 conversions: one op per unit of work. Any paid plan with overage enabled lets you run past the allowance ($0.02/op); otherwise the cap is hard.
| Code | Condition |
|---|---|
402 |
The monthly ops allowance is exhausted. All V2 endpoints (perceive, discover, lookup, distill, ingest) bill this one counter alongside V1 conversions. |
402 |
The active-watcher limit (max_watchers) is reached (watch). Watchers are a separate cap and never consume ops. |
402 |
The endpoint is switched off for the plan. V2 gates answer 402, unlike V1 feature gates, which answer 403. |
403 |
The endpoint is not in the API key's allowed_endpoints allowlist. |
Two V2 behaviours are deliberately not errors. Exhausting the AI-credit balance does not fail the request: schema extraction falls back to the heuristic and CSS result. And a multi-URL distill run that crosses the ops boundary part-way does not 402 either: it stops there, returns the URLs it finished, and appends a warning naming how many were skipped.
Validation (422)#
Every V2 request schema rejects unknown keys, so a misspelled parameter is a 422 naming the field. The body shape is described under 422 Unprocessable Entity.
| Endpoint | Condition |
|---|---|
| perceive | proxy_url, geolocation, or action_chain was sent; these are reserved for a later release. |
| distill | Neither schema nor prompt was supplied (sending both is fine, schema wins); 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 | frequency_minutes below the 60-minute hourly floor; an empty PATCH body. |
| 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 endpoint depends on an upstream search provider. Raw provider error text never reaches the client.
| Code | Message | Condition |
|---|---|---|
502 |
The search provider returned an error. Please try again. |
The provider returned an error response or a non-retryable transport fault. |
503 |
Search is temporarily unavailable. Please try again later. |
The provider is misconfigured (missing key) or temporarily unreachable. 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 is always asynchronous: POST /v2/ingest answers 202 with a job_id you poll. Perceive batches larger than 10 URLs answer 202 with status queued. A batch of 10 or fewer normally answers inline, but if it outruns the inline window it degrades to 202 with status processing and a warning, so handle 202 on any batch size.
A 202 also means later failures are not HTTP errors. Poll the job and read its status payload, as described in Sync and async jobs.
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_...) 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.,
.jsonfor json-to-xml,.docxfor doc-to-pdf). - Getting 413? Your file exceeds the plan's size limit. Read
detail.max_sizefrom the response, then check your plan's max file size or upgrade. - Getting 402? You've hit your monthly ops allowance, the watcher cap, or the storage limit, or the project has no active billing period. Check usage in the dashboard and see Rate limits and quotas.
Feature Access Issues#
- Getting 403 about plan features? The V1 feature you're trying to use (async, batch, webhooks, ZIP output, basic auth) requires a higher plan tier. Check the plan feature table.
- Getting 402 on a V2 endpoint that isn't about quota? V2 endpoint gates answer
402, not403. The message reads... is not available on your current plan. Upgrade to a V2-inclusive plan to access this endpoint.
Frequently asked questions#
Why does the API return 402 Payment Required for a file conversion?#
A 402 means a usage limit is exhausted: your unified monthly ops allowance (500 ops on the Founding plan), the active-watcher cap, or your project's storage allocation. It also covers two non-quota cases: a project with no active billing period, and a V2 endpoint that is switched off on your plan. Batch requests that would exceed the remaining monthly ops allowance are rejected upfront with a 402 for the entire batch. V1 conversions and V2 operations draw from the same allowance; any paid plan with overage enabled ($0.02/op) lets you run past it. Rate limiting is a different mechanism and answers 429.
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, since 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, measured from the uploaded part's exact byte count before any conversion work starts. A file exactly at the limit is accepted. The 413 body nests a structured object under detail, with file_size, max_size (both in bytes), tier, and key_type, so read it as detail.max_size rather than as a top-level field.
Can I use a private API key from browser JavaScript?#
No. A private key (sk_...) 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.