# How to Integrate Automatic Document Extraction into Your System via API (A Practical Guide for Technical Teams)

> A practical guide to integrating a document extraction API: authenticate, upload, poll or use a webhook, consume the JSON, and make retries safe.

- Source: https://parserai.co/blog/how-to-integrate-automatic-document-extraction-into-your-system-via-api-a-practical-guide-for-technical-teams
- Published: 2026-03-25 (updated 2026-10-19)

---
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](https://parserai.co/api-docs), and the [Get started](https://parserai.co/api-docs/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](https://parserai.co/blog/ocr-extraction-in-2026-how-to-automate-document-processing-for-faster-more-accurate-workflows) 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](https://parserai.co/security)), so keep a copy of anything you must retain.

In practice you create an API key on the console's [API keys](https://console.parserai.co/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.

check-key.sh — set the key and check it

```
# 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 to
curl '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](https://parserai.co/blog/jwt-iam-and-auth0-authentication-and-authorization-explained-without-the-confusion) 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).

upload.sh — send a document

```
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

Derive a key from something stable — the source document hash, or a business identifier like supplier plus invoice number — and store it next to the `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.

status.sh — check a document, then fetch its 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.

result.json — an extracted invoice (invented values)

```
{
  "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 `total` to `gross_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:

parser-client.ts — a thin wrapper instead of an SDK

```
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 {
  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  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=&created_from=
  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

If several services in different languages call the extraction API, generate clients from the OpenAPI spec rather than hand-writing one per language — but keep retries, the duplicate guard and webhook verification in a shared wrapper around the generated code, since generators rarely get those right.

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

_Illustrative: processing time grows with document length. These are not Parser timings; measure your own._

Design for the tail, not the average

Track p50 and p95 latency, not just the mean. A pipeline that looks fast on average can still time out on the long documents that matter most — size your timeouts and queue workers for the p95 case.

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

Parser

## See it on your own documents.

In the demo we process your documents live — no slides, no fluff.

[Book a demo](https://parserai.co/demo)
