If your backend receives invoices as PDFs — or worse, as scanned images — you've probably lived some version of this pipeline: run the file through a text extractor, get back a soup of unstructured strings, and then write an ever-growing pile of regexes to fish out the invoice number, the date, and the total. It works for one vendor. Then vendor number two arrives with a different layout, and the regexes multiply.
This guide walks through the alternative: sending the document to an AI invoice parser API and getting clean, schema-shaped JSON back from a single API call — upload and extraction in one request, scanned PDFs and photos included.
The problem with parsing PDF text yourself
A PDF is a drawing format, not a data format. When you extract raw text from an invoice, you typically get something like this:
ACME INDUSTRIAL SUPPLY CO. INVOICE
INV-2481 Date: 06/30/2026 Terms Net 30
M8 hex bolts (box of 500) 4 62.50 250.00
Angle grinder disc 115mm 20 3.10 62.00
SUBTOTAL 312.00
TAX 8.25% 25.74 TOTAL 337.74Column boundaries are gone, label/value pairs run together, and the visual structure a human relies on has been flattened away. Every heuristic you write against this text is coupled to one vendor's layout — and if the "PDF" is actually a scan, there is no text layer at all until you bolt on an OCR step. The fix is to stop parsing text and ask for structured data directly.
Step 1: Define your fields once
Create a free ParseDirect account (50 pages/month, no card required) and create a template — the list of fields you want back, described in plain language. For invoices, something like: invoice_number (string), vendor_name (string), invoice_date (date), total_amount (number), and line_items — a table field with description, quantity, and unit_price columns.
This template is nota zone map tied to one layout. It works for every invoice from every vendor, because the AI reads the document semantically rather than looking at coordinates. Then grab an API key from Settings — you'll get a pd_... token, shown once.
If you ever need your template's ID programmatically, list your templates:
curl https://parsedirect.com/api/v1/templates \
-H "Authorization: Bearer pd_your_api_key"Step 2: One API call — upload and extract together
POST the file as multipart/form-data and pass your template_id in the same request. ParseDirect uploads the document, runs the extraction, and returns the structured result in one response:
curl https://parsedirect.com/api/v1/documents \
-H "Authorization: Bearer pd_your_api_key" \
-F "file=@invoice-2481.pdf" \
-F "template_id=your_template_id"The response is JSON that's ready to use as-is:
{
"document": {
"id": "9b2e6c1a-...",
"filename": "invoice-2481.pdf",
"status": "completed"
},
"extraction": {
"status": "success",
"page_count": 2,
"extracted_data": {
"invoice_number": "INV-2481",
"vendor_name": "Acme Industrial Supply Co.",
"invoice_date": "2026-06-30",
"total_amount": 337.74,
"line_items": [
{
"description": "M8 hex bolts (box of 500)",
"quantity": 4,
"unit_price": 62.50
},
{
"description": "Angle grinder disc 115mm",
"quantity": 20,
"unit_price": 3.10
}
]
}
}
}Notice what you didn'thave to do: no OCR configuration, no regexes, no layout templates, no cleanup pass. The output is constrained to your template's schema — the fields you defined, with the types you defined. total_amountcomes back as an actual number, not the string "$337.74", and line_items is a real array you can iterate over.
The same call in Python and Node.js
Python
import requests
resp = requests.post(
"https://parsedirect.com/api/v1/documents",
headers={"Authorization": "Bearer pd_your_api_key"},
files={"file": open("invoice-2481.pdf", "rb")},
data={"template_id": "your_template_id"},
)
resp.raise_for_status()
data = resp.json()["extraction"]["extracted_data"]
print(data["invoice_number"], data["total_amount"])
for item in data["line_items"]:
print(f'- {item["description"]}: {item["quantity"]} x {item["unit_price"]}')Node.js
import { readFile } from "node:fs/promises";
const form = new FormData();
form.append(
"file",
new Blob([await readFile("invoice-2481.pdf")], { type: "application/pdf" }),
"invoice-2481.pdf",
);
form.append("template_id", "your_template_id");
const resp = await fetch("https://parsedirect.com/api/v1/documents", {
method: "POST",
headers: { Authorization: "Bearer pd_your_api_key" },
body: form,
});
const { extraction } = await resp.json();
console.log(extraction.extracted_data);Scanned PDFs and photos: the exact same call
This is where most pipelines fall over. A scanned invoice has no text layer, so text extractors return nothing, and legacy tools route you through a separate OCR engine. With ParseDirect there is no separate path: the model reads documents visually, so a scanned PDF, a JPEG, a PNG, or a phone photo of a receipt goes through the same endpoint with the same template — just change the filename in the -F "file=@..." line. A scan costs the same as a born-digital PDF: one page is one page.
Getting CSV instead of JSON
The API speaks JSON, and JSON is the right format for a backend. If a teammate needs a spreadsheet, every extraction can be exported as CSV from the dashboard with one click — or convert in a few lines of code, since the structure is already flat and predictable:
import csv, sys
writer = csv.DictWriter(sys.stdout, fieldnames=["description", "quantity", "unit_price"])
writer.writeheader()
writer.writerows(data["line_items"])Production notes
- Retries are safe. Re-sending an extraction for the same document and template returns the cached prior result instead of billing you again. Pass
force: truetoPOST /api/v1/documents/{id}/extractonly when you deliberately want a re-run. - Limits. PDFs up to 20MB, images up to 10MB, and 60 requests per minute per API key. Errors come back as JSON with conventional status codes (
402when you're out of page credits,429when rate-limited). - Two-step flow if you prefer. Omit
template_idon upload and call the extract endpoint later — useful when users pick a template after uploading.
The full endpoint reference lives in the API docs.
Try it with your own invoices
The whole setup — account, template, API key, first successful extraction — takes about five minutes. Sign up free, define your invoice fields, and run the curl command above against a real document. The first 50 pages every month are free, so you can evaluate it on your actual documents rather than a demo.