Parsli documentation

Build with the API

Extract a document

POST a base64 file, get structured JSON back in the same response. The inline route, its limits, and how results are shaped.


POST /api/v1/extract

Sends one document and returns the result in the same response.

Headers

HeaderValue
AuthorizationBearer ext_... (required)
Content-Typeapplication/json (required)

Body

FieldTypeRequiredNotes
file.datastringYesThe file, base64-encoded
file.namestringYesOriginal filename, including extension
file.typestringRecommendedMIME type, e.g. application/pdf

file.data and storage_path are alternatives — send one. storage_path is the large-file route.

Request

bash
curl -X POST https://parsli.co/api/v1/extract \
  -H "Authorization: Bearer ext_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file": {
      "name": "invoice.pdf",
      "type": "application/pdf",
      "data": "JVBERi0xLjQKJcfs..."
    }
  }'
javascript
const data = await fs.readFile("invoice.pdf", { encoding: "base64" })

const response = 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({
    file: { name: "invoice.pdf", type: "application/pdf", data },
  }),
})

const { results } = await response.json()
python
import base64, os, requests

with open("invoice.pdf", "rb") as f:
    data = base64.b64encode(f.read()).decode()

response = requests.post(
    "https://parsli.co/api/v1/extract",
    headers={"Authorization": f"Bearer {os.environ['PARSLI_API_KEY']}"},
    json={"file": {"name": "invoice.pdf", "type": "application/pdf", "data": data}},
)

results = response.json()["results"]

Response

json
{
  "success": true,
  "parser_id": "6f3a1c88-...",
  "document_id": "b71c9d02-...",
  "results": {
    "invoice_number": "INV-2026-0417",
    "vendor_name": "Northgate Supplies",
    "total_amount": 1284.5,
    "due_date": "2026-09-01",
    "line_items": [
      { "description": "Steel brackets", "quantity": 40, "unit_price": 12.5, "total": 500.0 },
      { "description": "Delivery", "quantity": 1, "unit_price": 784.5, "total": 784.5 }
    ]
  }
}
KeyTypeNotes
successbooleanWhether extraction completed
parser_idstringThe parser the key is scoped to
document_idstringUse it to re-fetch this result later
resultsobjectKeyed by your field names

How results are shaped

results mirrors your Field definition exactly:

  • A leaf field is a scalar of its Field type — a decimal comes back as a JSON number, not a string.
  • An Object field is a nested object.
  • A Table field is an array of objects, one per row, keys matching your column names.
  • A field the engine could not ground in the document is null, never a guess.

That last point is the one to design around. Check for null and route it to a human; do not treat absence as zero.

Limits

Inline requests are bounded twice over — by how much base64 fits in a request body, and by how long one HTTP request may run. Both are real ceilings, and a big scan will hit them.

Base64 inflates a file by roughly a third, so a file comfortably under the cap on disk may not be once encoded.

Anything large, multi-page, or slow belongs on the upload-and-poll route. Files over 300MB must use it.

Cost

One Credit per Page, charged on the document's real page count. Credits are reserved before processing and refunded if extraction fails — a 500 costs you nothing. See how pages are counted.

Errors

See errors for every status code. The two worth handling explicitly: 402 when you are out of credits, and 503 when a sync extraction times out — the second is a signal to move that file to the async route.

Something here wrong or missing? Tell us — we treat it as a bug.