Parsli documentation

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:

json
{ "error": "Parser not found or inactive" }

Status codes

CodeMeaningRetry?What to do
200Success
400Malformed requestNoFix the request. See below.
401Bad or missing API keyNoCheck the Authorization header and that the key is not revoked
402Out of creditsNot until topped upAdd credits or subscribe
403storage_path outside your namespaceNoUse a path returned by /api/v1/upload-url
404Parser not found, inactive, or document not foundNoCheck the parser is active
500Extraction failedYes, onceCredits were refunded
503Timed out or temporarily unavailableYes, with backoffMove 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.

javascript
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

javascript
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.