Errors and troubleshooting
The shape of an error
Errors are JSON with a human-readable detail:
{"detail": "Page quota exceeded (50/50 monthly pages used, 0 welcome pages left). ..."}
Validation errors (status 422) have a list in detail, one entry per problem, with its location, message and type:
{"detail": [{"loc": ["body", "file"], "msg": "Field required", "type": "missing"}]}
The answer never repeats the value you sent.
Status codes
| Status | Meaning | What to do |
|---|---|---|
400 |
A parameter is not valid: unknown format or mode, an invalid schema, an invalid webhook_url |
Read detail and fix the request; do not retry unchanged |
401 |
The API key is missing, wrong or revoked | Check the Authorization: Bearer header and that the key is still active |
402 |
The page quota is used up (the month and the welcome pages), or the document needs more pages than you have left | Wait for the renewal on the first of the month, or contact support |
413 |
The file is over 50 MB, or the batch is over 100 files or 200 MB | Split the upload |
422 |
A required field is missing or malformed (for example file) |
Check the field names and the multipart/form-data encoding |
429 |
Too many conversion requests per minute | Wait for Retry-After seconds, then retry |
500 |
The document could not be processed | See below |
503 |
An image could not be read because the OCR provider did not answer; it comes with Retry-After: 60 and no pages are charged |
Wait and retry |
When a document cannot be read (500)
In synchronous mode a document that cannot be read (corrupted, or in a format that is not supported such as a binary .doc) currently answers 500 with a message that starts with Document parsing error: followed by the reason, for example that a .doc file is not supported and should be saved as .docx or PDF. Treat it as a problem with that file: check it opens normally and try again, or convert it first. This behaviour may be refined in the future, so rely on the message rather than on the code alone.
In a batch the same problem is reported on the file itself (status: "error") and the other files carry on. In an asynchronous job it appears as status: "failed" with the reason in error_message.
A handling strategy
- Retry
429and503after the time inRetry-After, with a limit on attempts. - Do not retry
400,401,402,413or422: they will fail again until you change something. - Log the status, the
detailand, for jobs, thejob_id: they are the first things support will ask for. - Never log your API key.
# curl retries 429 and 503 by itself, waiting for the Retry-After the API sends
curl -s --retry 3 --fail-with-body https://api.docslice.app/v1/convert \
-H "Authorization: Bearer $DOCSLICE_API_KEY" \
-F "file=@invoice.pdf" \
-F "format=json"
import json
import os
import time
import requests
api_key = os.environ["DOCSLICE_API_KEY"]
def convert(path):
for attempt in range(4):
with open(path, "rb") as handle:
response = requests.post(
"https://api.docslice.app/v1/convert",
headers={"Authorization": f"Bearer {api_key}"},
files={"file": (path, handle)},
timeout=120,
)
if response.status_code in (429, 503): # too many requests, or the provider is busy: wait and retry
time.sleep(int(response.headers.get("Retry-After", "5")))
continue
if response.status_code == 402:
raise SystemExit("The monthly page quota is used up")
response.raise_for_status()
return response.json()
raise SystemExit("The service stayed busy: try again later")
print(json.dumps(convert("invoice.pdf"), indent=2))
const fs = require("node:fs");
async function convert(path) {
for (let attempt = 0; attempt < 4; attempt++) {
const form = new FormData();
form.append("file", new Blob([fs.readFileSync(path)]), path);
const response = await fetch("https://api.docslice.app/v1/convert", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.DOCSLICE_API_KEY}` },
body: form,
});
if (response.status === 429 || response.status === 503) {
// too many requests, or the provider is busy: wait and retry
await new Promise((resolve) => setTimeout(resolve, 1000 * Number(response.headers.get("Retry-After") || 5)));
continue;
}
if (response.status === 402) throw new Error("The monthly page quota is used up");
if (!response.ok) throw new Error(`HTTP ${response.status}: ${JSON.stringify(await response.json())}`);
return response.json();
}
throw new Error("The service stayed busy: try again later");
}
convert("invoice.pdf").then((data) => console.log(JSON.stringify(data, null, 2)));
<?php
function convert($path) {
for ($attempt = 0; $attempt < 4; $attempt++) {
$retryAfter = 5;
$curl = curl_init('https://api.docslice.app/v1/convert');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('DOCSLICE_API_KEY')],
CURLOPT_POSTFIELDS => ['file' => new CURLFile($path)],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
CURLOPT_HEADERFUNCTION => function ($curl, $line) use (&$retryAfter) {
if (stripos($line, 'Retry-After:') === 0) {
$retryAfter = (int) trim(substr($line, 12));
}
return strlen($line);
},
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($status === 429 || $status === 503) { // too many requests, or the provider is busy: wait and retry
sleep($retryAfter);
continue;
}
if ($status === 402) {
exit("The monthly page quota is used up\n");
}
if ($status >= 400) {
exit("HTTP $status: $body\n");
}
return json_decode($body, true);
}
exit("The service stayed busy: try again later\n");
}
echo json_encode(convert('invoice.pdf'), JSON_PRETTY_PRINT);
The full table of codes is in the error code reference.