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
| Header | Value |
|---|---|
Authorization | Bearer ext_... (required) |
Content-Type | application/json (required) |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
file.data | string | Yes | The file, base64-encoded |
file.name | string | Yes | Original filename, including extension |
file.type | string | Recommended | MIME type, e.g. application/pdf |
file.data and storage_path are alternatives — send one. storage_path is the large-file route.
Request
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..."
}
}'
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()
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
{
"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 }
]
}
}
| Key | Type | Notes |
|---|---|---|
success | boolean | Whether extraction completed |
parser_id | string | The parser the key is scoped to |
document_id | string | Use it to re-fetch this result later |
results | object | Keyed 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
decimalcomes 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.