All API errors follow a consistent format:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description of the error."
}
}
OAuth endpoints use a different format per RFC standards:
{
"error": "error_code",
"error_description": "Human-readable description."
}
| Status |
Meaning |
200 |
Success |
201 |
Created (share link, API key) |
202 |
Accepted (async job queued) |
204 |
No Content (successful deletion) |
400 |
Bad request — invalid parameters |
401 |
Unauthorized — missing or invalid credentials |
402 |
Payment required — insufficient credits |
403 |
Forbidden — valid credentials, insufficient permissions or quota exceeded |
404 |
Not found — resource doesn't exist |
413 |
Payload too large — file exceeds size limit |
415 |
Unsupported media type — invalid file type |
422 |
Unprocessable — valid request but cannot process (e.g., encrypted PDF) |
429 |
Rate limit exceeded |
500 |
Internal server error |
501 |
Not implemented — feature not available |
503 |
Service unavailable — temporary outage |
| Code |
HTTP |
Description |
UNAUTHORIZED |
401 |
No Authorization header or token is invalid |
AUTH_REQUIRED |
401 |
Endpoint requires authentication |
FORBIDDEN |
403 |
Valid credentials but action not allowed |
ACCOUNT_SUSPENDED |
403 |
Account has been suspended |
| Code |
HTTP |
Description |
INVALID_PARAMS |
400 |
Request parameters failed validation |
INVALID_OPTIONS |
400 |
Operation options are invalid |
INVALID_FILE_TYPE |
415 |
Uploaded file is not a supported format |
FILE_TOO_LARGE |
413 |
File exceeds your plan's size limit |
| Code |
HTTP |
Description |
ENGINE_ERROR |
500 |
PDF processing engine encountered an error |
JOB_NOT_FOUND |
404 |
Job ID does not exist |
FILE_NOT_FOUND |
404 |
File does not exist or access denied |
| Code |
HTTP |
Description |
RATE_LIMITED |
429 |
Too many requests per minute |
QUOTA_EXCEEDED |
403 |
Monthly operation limit reached |
| Code |
HTTP |
Description |
KEY_LIMIT_REACHED |
403 |
Maximum API keys for your plan reached |
KEY_NOT_FOUND |
404 |
API key ID does not exist or already revoked |
| Code |
HTTP |
Description |
INSUFFICIENT_CREDITS |
402 |
Not enough credits for this operation |
| Code |
HTTP |
Description |
invalid_request |
400 |
Malformed OAuth request |
invalid_client_metadata |
400 |
Registration validation failure |
invalid_grant |
400 |
Invalid, expired, or already-used authorization code/token |
invalid_scope |
400 |
Requested scope is invalid |
access_denied |
403 |
User denied the authorization request |
oauth_not_configured |
501 |
OAuth is not available on this server |
const response = await fetch(url, options);
const data = await response.json();
if (!response.ok) {
const code = data.error?.code || data.error;
switch (code) {
case "UNAUTHORIZED":
// Refresh token and retry
break;
case "RATE_LIMITED":
// Wait and retry with exponential backoff
const retryAfter = parseInt(
response.headers.get("Retry-After") || "10"
);
await sleep(retryAfter * 1000);
break;
case "QUOTA_EXCEEDED":
// Upgrade plan or wait for monthly reset
break;
case "ENGINE_ERROR":
// Report to support with the error message
break;
default:
console.error(`API error: ${data.error?.message || data.error_description}`);
}
}
Tip
Always check the code field rather than parsing the message string. Error messages may change, but error codes are stable.