How to Integrate Automatic Document Extraction into Your System via API (A Practical Guide for Technical Teams)
Automatic document extraction has moved from a nice-to-have to a load-bearing part of any document-heavy system. This is a hands-on guide for engineers who want to wire an intelligent document processing (IDP) API into their stack — cleanly, safely, and in a way that survives production volume.
The problem you are actually solving
When invoices, receipts, and contracts arrive faster than people can read and type them, humans become the throughput ceiling. Worse, manual transcription leaks errors into accounting, compliance, and reporting downstream. The fix is to treat extraction as a pipeline: documents go in, validated structured data comes out, and your systems consume it without a person in the loop for the happy path.
An extraction API like Parser turns a document into JSON: you define the fields in an extraction template, upload a document, and read the fields back. Your job as the integrator is the plumbing around it: ingestion, authentication, orchestration, validation, and mapping into your own schema. The endpoint reference is the API reference, and the Get started page walks the same flow call by call.
This guide assumes you are writing the integration yourself. If instead you are wiring extraction into an iPaaS or workflow builder, or you still need to make the case for automating the workflow at all, the OCR extraction and document automation overview covers the asynchronous pattern, duplicate guards on platform retries, and how to decide where to start.
3 calls
upload, check the status or wait for a webhook, fetch the result
202
what the upload returns: Accepted, with ids, not the result
1 key
per integration, created in the console and revocable on its own
The core flow at a glance
Almost every API-based extraction integration follows the same repeatable shape. Keep these stages decoupled and the architecture stays modular — documents in, structured data out.
- Ingest — a document enters your system (email inbox, upload portal, SFTP, mobile capture).
- Upload — you send the file to the extraction API (or to a signed URL, for a large file) and get back a document id and an extracted-document id.
- Process — the service reads the document asynchronously.
- Retrieve — you learn it is done via webhook or by checking the status, then fetch the JSON.
- Validate & enrich — apply schema checks, business rules, and master-data lookups.
- Write back — sync into the ERP, database, or warehouse.
- Monitor — track latency, error rates, and manual-review share.
Step 1 — Authenticate and secure the integration
Before a single document moves, get auth right. Extraction payloads often contain personal or financial data, so privacy-by-design is not optional.
- Keep the API key on your server, in an environment variable or a secrets manager — never in browser or mobile code, source, or a committed config file.
- Give each integration its own key, named after it, so it can be rotated or revoked on its own.
- Call the API over TLS only, and protect your own copies of uploads and extracted results at rest.
- Apply your own retention policy and log access, especially for contracts. Parser deletes uploaded documents after a retention period (see the security page), so keep a copy of anything you must retain.
In practice you create an API key on the console's API keys page (New API key, named after the integration), copy it once — it starts with prs_ and is not shown again — and send it as an Authorization: Bearer header on every request. A key acts as your account. Keep it on your server, in an environment variable or a secret store: never in browser or mobile code, a repository, tickets or chat. If one leaks, revoke it and create a new one.
# The key from API keys > New API key (shown once). Keep it out of your code.export PARSER_API_KEY='prs_...'# Check it: returns the account the key belongs tocurl 'https://api.parserai.co/profiles/me' \ -H "Authorization: Bearer $PARSER_API_KEY"A key does not expire: it works until you revoke it on the same page, and a revoked key is refused with a 401 at once. To rotate one with no downtime, create the new key and deploy it, wait until the old key's Last used stops changing in the console, then revoke the old one. An account can hold up to 10 active keys.
Already signing in with POST /auth/login? It keeps working. Its token lasts 1 hour, so your script has to keep your password and sign in again: move to a key when you can, nothing else in your integration changes.
The key Parser issues is for calling Parser. If your own services also call each other with JWTs, the validation rules on the receiving side are stricter than they look — signature, issuer, audience, expiry, then scopes, and only then a resource-level check. JWT, IAM and Auth0 explained covers that sequence and where each step belongs.
Step 2 — Upload the document and start extraction
For anything beyond a trivial demo, treat extraction as asynchronous, which is how the API works: you post the file with POST /documents/upload and it answers 202 Accepted at once, with the ids of the new document and of its extraction. Processing happens in the background. This keeps your request threads free during peak windows. For a large file, ask for a signed URL (POST /documents/upload/generate-signed-url) and PUT the file to it: PDFs up to 200 MB, other files up to 50 MB.
Two details matter here. First, every upload names the extraction template to read the file against, as entity_group_id: create the template once (POST /entity-groups/with-entities) and reuse its id. Second, the API has no idempotency key, so a retried upload creates a second document. Guard against that on your side (see the note below).
curl -X POST https://api.parserai.co/documents/upload \ -H "Authorization: Bearer $PARSER_API_KEY" \ -F "file=@invoice-INV-2026-00814.pdf" \ -F "entity_group_id=$ENTITY_GROUP_ID"# Response — 202 Accepted# {# "message": "Document accepted for processing.",# "documentId": "your-document-id",# "extractedDocumentId": "your-extracted-document-id",# "entityGroupId": "your-entity-group-id"# }Make retries safe yourself
documentId you get back. If a request times out before you see the response, do not just send the file again: list your documents first (GET /documents/ filters by title and creation time) and upload only if the first attempt is not there. A second upload is a second extraction, and it is charged again.Step 3 — Get the result: webhook or poll
Once the document is processed you need to know about it. There are two idiomatic patterns, and you can support both.
Webhooks are best for event-driven systems: you register a URL once (POST /webhooks/) and attach the webhook to your template (PUT /entity-groups/{entity_group_id} with its webhook_id, or pass webhook_id when you create the template). A webhook fires only for the templates it is attached to: the service POSTs each extraction to your URL when it completes or fails, so you avoid constant polling. A document refused before extraction (too large, limit reached, insufficient credits or an unsupported file) sends no webhook, so poll its status too. Respond quickly with a 2xx and do the heavy work on your own queue — never block the webhook handler on ERP writes. The API reference describes no signature on these calls, but a webhook can carry custom headers: put a long random value in one and have your endpoint reject any request without it. Every call is logged (GET /webhooks/logs shows the status your endpoint returned), and the reference says nothing about retries of a failed call, so keep a status check as a fallback.
Polling is simpler to reason about and a good fallback when you can't expose a public endpoint. Check GET /documents/status/{documentId} with exponential backoff — start around one second and back off toward a ceiling — rather than hammering the API on a tight loop. The terminal states are completed, failed, too large, limit reached and insufficient credits; only a completed document has a result.
curl https://api.parserai.co/documents/status/your-document-id \ -H "Authorization: Bearer $PARSER_API_KEY"# Response: a bare JSON string# "completed"curl https://api.parserai.co/extracted-documents/your-extracted-document-id \ -H "Authorization: Bearer $PARSER_API_KEY"Step 4 — Consume the structured JSON
The whole point of the integration is this payload. The fields you defined in the template come back as the keys of the objects in extracted_data, next to an extraction_status. The result carries no confidence score (the extracted document in the OpenAPI spec has no such field), so validate with rules, not thresholds. Design your consumer against the template's fields, not against a single sample.
{ "id": "your-extracted-document-id", "document_id": "your-document-id", "entity_group_id": "your-entity-group-id", "extraction_status": "success", "extracted_data": [ { "supplier": "ACME Components Ltd.", "invoice_number": "INV-2026-00814", "issue_date": "2026-02-11", "due_date": "2026-03-12", "currency": "USD", "subtotal": 4820.00, "tax": 385.60, "total": 5205.60 } ]}Because the keys are fixed, you can validate deterministically: check that required fields are present, that total equals subtotal plus tax, that the currency code is valid, and that dates fall in an acceptable range. Anything that fails routes to an exception workflow instead of contaminating the ERP.
Step 5 — Validate, then map to your schema
Extraction output rarely matches your internal model exactly, and it shouldn't have to. Put a thin mapping layer between the API and your database so the two can evolve independently.
- Rename fields to your conventions — for example
totaltogross_amount. - Convert formats: dates, decimal separators, and locale-specific numbers.
- Derive fields your system needs, like net-term days or tax rate.
- Attach metadata — the document ID, a correlation ID, and a processing timestamp — for traceability.
Version this mapping layer. When the template's fields or your ERP change, a versioned transform means you can migrate deliberately instead of firefighting broken consumers.
Do you need a document extraction SDK?
Teams evaluating an intelligent document extraction service often ask for an SDK first. It is a reasonable instinct — an SDK promises typed responses, built-in retries and less boilerplate — but for an extraction API the surface is small enough that the question is really about who owns the plumbing. The whole contract is the three calls above: upload, learn that the document finished, fetch the result. Any HTTP client in any language can make them.
Parser documents no SDK. It exposes one REST API, all at the root with no version prefix, and an OpenAPI description at api.parserai.co/openapi.json, so you can call it directly or generate a typed client from the spec with the generator your stack already uses. What an SDK would add is not access — it is the four behaviours below. Those are what you actually need, whether they come from a vendor package or from about eighty lines of your own code.
- Retries with backoff on 5xx (and 429, should you ever see one) for calls that are safe to repeat, such as reads — never on 4xx validation errors, which will fail the same way every time.
- A duplicate guard on every upload, a key you generate once per document and store with the returned
documentId, so a timeout never produces a second document by accident. - Typed results — the response parsed into your own type and validated at the boundary, so a change in the template's fields fails loudly in one place instead of quietly downstream.
- Webhook authentication with a shared secret in a custom header, compared in constant time, rejecting any callback that lacks it.
Writing that wrapper yourself has a practical advantage over a vendor SDK: it lives in your repository, follows your logging and error conventions, and has no transitive dependencies to audit or upgrade. The trade-off is that you own it — but it is small, it changes only when your integration changes, and it is the single place the rest of your codebase talks to the extraction service. A minimal TypeScript version looks like this:
const BASE = "https://api.parserai.co";const RETRYABLE = new Set([429, 500, 502, 503, 504]);async function call(path: string, init: RequestInit = {}, attempt = 0): Promise<Response> { const res = await fetch(BASE + path, { ...init, headers: { Authorization: `Bearer ${process.env.PARSER_API_KEY}`, ...init.headers }, }); // Only reads are repeated here: an upload is not retried blindly (see submit). const repeatable = (init.method ?? "GET") === "GET"; if (repeatable && RETRYABLE.has(res.status) && attempt < 5) { const wait = Math.min(1000 * 2 ** attempt, 30_000) + Math.random() * 250; await new Promise((r) => setTimeout(r, wait)); return call(path, init, attempt + 1); } if (!res.ok) throw new Error(`parser ${res.status}: ${await res.text()}`); return res;}export async function submit(file: File, entityGroupId: string) { const form = new FormData(); form.append("file", file); form.append("entity_group_id", entityGroupId); // There is no idempotency key. If this throws after the request left, look for the file first: // GET /documents/?search=<file name>&created_from=<time of the attempt> const res = await call("/documents/upload", { method: "POST", body: form }); return (await res.json()) as { documentId: string; extractedDocumentId: string };}The key detail is that the upload is not retried inside the loop. With no idempotency key, a timeout leaves you unsure whether the file landed, so the safe move is to look first (the documents list filters by title and creation time) and send it again only if it is not there. Add a getResult function that validates the payload against the template's fields, and a verifyWebhook function for the callback route, and you have everything a vendor SDK would give you for this API.
When a generated client is worth it
Latency: set expectations by input type
Processing time is not uniform: expect a long document to take longer than a short one, and measure your own files rather than trusting a number from a blog. Knowing the shape of your own distribution lets you set sane timeouts and SLAs per document type rather than one blunt global value.
Short document
Medium document
Long document
Very long document
Design for the tail, not the average
Patterns that scale without a maintenance nightmare
A few well-worn patterns keep the integration healthy as volume grows:
- Queue-based orchestration — submit jobs onto a queue so throughput and retries are managed centrally, with correlation IDs threaded through for traceability.
- Batch processing for backfills — ideal when migrating a legacy archive or reprocessing historical documents, kept separate from the real-time lane.
- Human-in-the-loop for exceptions — route only the documents that fail your rules to review, and capture the corrections so each one becomes a new rule.
- Observability everywhere — latency percentiles, error rates by document type and supplier, manual-review share, and top failing fields.
Frequently asked questions
How does API-based document extraction work?
Your system uploads a document to the extraction API, the service processes it with OCR and AI models, and it returns structured results — typically JSON — that you validate and map into your databases, ERPs, or analytics tools.
Should I use webhooks or polling?
Use webhooks when you can expose a public endpoint that checks a shared secret and want near real-time orchestration. Use polling with exponential backoff when you can't, or as a resilient fallback. Supporting both is common.
Is there an SDK for document extraction APIs?
Some vendors ship one; Parser documents none, because the API is a handful of REST calls with an OpenAPI spec. A thin wrapper of your own — retries on 5xx for reads, a duplicate guard per document, typed results and a webhook secret check — covers what an SDK would, without an extra dependency.
What is the biggest integration challenge?
It is rarely uploading files. The hard part is guaranteeing the extracted results match your internal schema and business rules — which is why a strong validation and mapping layer plus an exception workflow are what make the pipeline production-stable.
How do I avoid processing the same document twice?
The API has no idempotency key, so the guard is yours. Derive a key from a stable value such as the document hash, store it next to the documentId you receive, and after a timeout list your documents (they filter by title and creation time) before you upload again. A second upload is a second extraction, and it is charged again.
Build a pipeline, not a demo
Integrating automatic document extraction via API is one of the fastest ways to modernize a document-heavy operation — but the demo (upload a file, print the JSON) is the easy 20%. The durable version treats extraction as a pipeline: authenticate, upload once, retrieve asynchronously, validate against the template's fields, map deliberately, and monitor the tail. Get those pieces right and you get a foundation that scales across departments instead of a script that breaks the first time a vendor changes their invoice layout.