Build with the API
Errors
Every status code the API returns, what causes it, and whether retrying will help.
Errors return a JSON body with a single error key:
{ "error": "Parser not found or inactive" }
Status codes
| Code | Meaning | Retry? | What to do |
|---|---|---|---|
200 | Success | — | — |
400 | Malformed request | No | Fix the request. See below. |
401 | Bad or missing API key | No | Check the Authorization header and that the key is not revoked |
402 | Out of credits | Not until topped up | Add credits or subscribe |
403 | storage_path outside your namespace | No | Use a path returned by /api/v1/upload-url |
404 | Parser not found, inactive, or document not found | No | Check the parser is active |
500 | Extraction failed | Yes, once | Credits were refunded |
503 | Timed out or temporarily unavailable | Yes, with backoff | Move the file to the async route |
The ones worth handling explicitly
400 — malformed request
Three distinct causes, and the message tells you which:
Invalid JSON— the body did not parse.Provide either file.data (base64) or storage_path— neither was supplied.Parser has no fields— a Field Extraction parser with an empty Field definition. Add fields before calling it.
None are retryable. The request is wrong.
402 — out of credits
Extraction stops. Nothing is lost — parsers, field definitions, integrations, and past results are all intact, and everything resumes when credits are added.
Retrying will not help until the balance changes. Alert rather than backing off, or you will retry silently for hours. See free pages and plans.
500 — extraction failed
Something went wrong processing the document. Credits are refunded, so a failure costs you nothing.
Retry once. If it fails twice on the same document, the document itself is likely the problem — a corrupt PDF, an encrypted file, an unreadable scan. Route it to a human rather than looping.
503 — timed out
Most often the sync route running out of wall-clock on a document that needed longer.
This is a routing signal, not a transient blip. Retrying the same file on the same route will usually time out again. Send it via upload-and-poll, which has no such ceiling.
Errors that are not HTTP errors
The most important failure mode returns 200.
If Parsli's parsing engine cannot ground a value in the document, that field comes back null. The request succeeded; the value is not there. That is a deliberate design choice — the engine abstains rather than inventing something plausible, because a confident wrong number in your ledger is far worse than a gap.
const { results } = await response.json()
// A 200 does not mean every field was found.
const missing = REQUIRED_FIELDS.filter((f) => results[f] == null)
if (missing.length) return flagForReview(documentId, missing)
Treat null as "a human needs to look at this", not as zero and not as an error.
Retry policy
const RETRYABLE = new Set([500, 503])
async function extractWithRetry(body, attempt = 0) {
const res = await fetch("https://parsli.co/api/v1/extract", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PARSLI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
if (res.ok) return res.json()
if (!RETRYABLE.has(res.status) || attempt >= 2) {
throw new Error(`Parsli ${res.status}: ${(await res.json()).error}`)
}
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000))
return extractWithRetry(body, attempt + 1)
}
Never retry 400, 401, 403, or 404 — the request will not become valid. Never blind-retry 402; it is a balance problem a human has to solve.
Something here wrong or missing? Tell us — we treat it as a bug.