Build with the API
Large and slow files
The three-step upload-and-poll route. No size ceiling, no request timeout — the only reliable way to handle documents you did not choose.
The inline route is bounded by request body size and request wall-clock. This route is bounded by neither: bytes go straight to storage without passing through the API, and a worker does the extraction.
Use it for anything large, multi-page, or slow — and for anything a user chose, because you cannot predict what they will upload.
The three steps
1. Ask for an upload URL
curl -X POST https://parsli.co/api/v1/upload-url \
-H "Authorization: Bearer ext_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "file_name": "statement.pdf", "file_type": "application/pdf" }'
{
"upload_url": "https://...storage.../object/upload/sign/...",
"storage_path": "8c1f.../6f3a.../api/9b2e.../statement.pdf",
"expires_in": 7200
}
expires_in is seconds. Upload within that window or request a fresh URL.
2. PUT the bytes to that URL
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @statement.pdf
This request does not carry your API key — the signed URL is the credential, and it is single-purpose and short-lived. The bytes never touch the Parsli API, which is precisely why there is no size ceiling here.
3. Submit the path for extraction
curl -X POST https://parsli.co/api/v1/extract \
-H "Authorization: Bearer ext_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"storage_path": "8c1f.../6f3a.../api/9b2e.../statement.pdf",
"file_name": "statement.pdf",
"file_type": "application/pdf",
"file_size": 48291043
}'
{
"success": true,
"document_id": "b71c9d02-...",
"status": "processing",
"poll_url": "/api/v1/documents/b71c9d02-..."
}
You get a document_id immediately. Extraction happens in the background.
Important: A storage_path submission is always processed asynchronously, even for a small file. You will get status: "processing" and never an inline result on this route. Do not write a client that expects results here.
Collecting the result
GET /api/v1/documents/{document_id}
curl https://parsli.co/api/v1/documents/b71c9d02-... \
-H "Authorization: Bearer ext_YOUR_API_KEY"
{
"document_id": "b71c9d02-...",
"status": "completed",
"file_name": "statement.pdf",
"page_count": 42,
"results": { "account_number": "...", "transactions": [] },
"error": null
}
status | Meaning |
|---|---|
processing | Still working. Poll again. |
completed | Done. results is populated. |
error | Failed. error explains why; credits were refunded. |
Polling politely
async function waitForResult(documentId, { timeoutMs = 15 * 60_000 } = {}) {
const started = Date.now()
let delay = 2_000
while (Date.now() - started < timeoutMs) {
const res = await fetch(`https://parsli.co/api/v1/documents/${documentId}`, {
headers: { Authorization: `Bearer ${process.env.PARSLI_API_KEY}` },
})
const doc = await res.json()
if (doc.status === "completed") return doc.results
if (doc.status === "error") throw new Error(doc.error ?? "Extraction failed")
await new Promise((r) => setTimeout(r, delay))
delay = Math.min(delay * 1.5, 30_000) // back off; large scans take minutes
}
throw new Error("Timed out waiting for extraction")
}
Start around two seconds and back off. A forty-page scan takes minutes, and polling it every second just burns both our budgets.
Better still: do not poll at all. Configure an outbound Webhook on the parser and Parsli calls you when the document finishes. See receiving results.
Choosing a route programmatically
const INLINE_LIMIT = 4 * 1024 * 1024 // conservative: base64 adds ~33%
async function extract(buffer, name, type) {
return buffer.byteLength > INLINE_LIMIT
? extractViaUpload(buffer, name, type)
: extractInline(buffer, name, type)
}
Size is the easy signal. The one it misses is a small file that is slow — a dense two-page scan can outlast an inline request. If you see 503 on inline extractions, move that class of document to this route.
Cost
Identical to the inline route: one Credit per Page, refunded on failure. The route you choose does not change the price.
Something here wrong or missing? Tell us — we treat it as a bug.