PDF Document Extraction
Pull structured data out of a PDF (delivery note, invoice, contract, prescription) against a JSON schema you control. Useful for any workflow where you receive scanned or generated PDFs from external parties and want to land structured data in your system without manual data entry.
The workflow at a glance
┌──────────────┐ ┌────────────┐ ┌──────────┐
│ Source │ │ Your app │ │ xander │
│ (mailbox, │ │ │ │ │
│ upload, …) │ │ │ │ │
└──────┬───────┘ └─────┬──────┘ └─────┬────┘
│ PDF │ │
│─────────────────>│ │
│ │ (optional) │
│ │ 1. upload PDF │
│ │────────────────>│
│ │ pdfId │
│ │<────────────────│
│ │ │
│ │ 2. extract with │
│ │ JSON schema │
│ │────────────────>│
│ │ structured │
│ │ result │
│ │<────────────────│
│ │ persist │
Step 1 (optional): Upload the PDF and get an ID
You have two options for getting the PDF bytes to xander:
- Inline base64 in the extraction request, simpler, single round-trip, good for small PDFs (under a few MB).
-
Upload first via
POST /pdfstore/uploadand reference by ID in the extraction request, better for larger PDFs and when you might run multiple extractions on the same document with different schemas (e.g. once for header data, once for line items).
If you go with the upload approach:
curl -X POST "$XANDER_BASE_URL/pdfstore/upload" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-F "pdf=@delivery-note-12345.pdf;type=application/pdf"
Response is a single integer (the PDF ID):
42
Use that ID in step 2 instead of the inline base64 file. The uploaded PDF is retained only as long as needed to serve subsequent extraction calls, see your contract for exact retention details.
Step 2: Extract structured fields against your JSON schema
POST /prompt/pdf-informationextraction-structured takes the PDF (inline
or by ID), a JSON schema describing the shape of the result, and an optional
free-form instruction. xander returns a JSON object matching your schema.
Inline (small PDFs)
curl -X POST "$XANDER_BASE_URL/prompt/pdf-informationextraction-structured" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"PdfInput": {
"PdfFile": {
"Base64Content": "<base64-encoded PDF bytes>",
"FileName": "delivery-note-12345.pdf"
}
},
"QueryParameter": [
{ "Name": "UserInformationRequest", "Value": "Extract supplier, recipient, delivery date, and all line items with quantity and unit." }
],
"OutputStructure": {
"JsonSchemaDefinition": "{\"type\":\"object\",\"properties\":{\"supplier\":{\"type\":\"string\"},\"recipient\":{\"type\":\"string\"},\"deliveryDate\":{\"type\":\"string\",\"format\":\"date\"},\"deliveryNoteNumber\":{\"type\":\"string\"},\"lineItems\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"articleNumber\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"quantity\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"}},\"required\":[\"description\",\"quantity\",\"unit\"],\"additionalProperties\":false}}},\"required\":[\"supplier\",\"recipient\",\"deliveryDate\",\"lineItems\"],\"additionalProperties\":false}"
},
"Options": {
"PromptIdentification": {
"ProjectId": "your-project-id",
"Promptname": "document-extraction/delivery_note"
},
"SessionMetadata": {
"UseCaseName": "pdf-document-extraction",
"UseCaseSessionId": "<your document id or correlation id>"
}
}
}'
By ID (larger PDFs)
"PdfInput": {
"PdfId": 42
}
Everything else stays the same, just swap PdfFile for PdfId.
Response shape
{
"result": {
"supplier": "ACME Industrial Supplies AG",
"recipient": "XYZ Manufacturing GmbH",
"deliveryDate": "2026-04-09",
"deliveryNoteNumber": "LS-2026-04-12345",
"lineItems": [
{
"articleNumber": "F7-A4-PLEAT",
"description": "Pleated air filter F7, 595x595x96",
"quantity": 12,
"unit": "pcs"
},
{
"articleNumber": "BELT-V-A38",
"description": "V-belt type A 38\"",
"quantity": 4,
"unit": "pcs"
}
]
},
"gptResult": { "plainResponse": "..." },
"metaInformation": { "retries": 0, "appliedModel": "..." }
}
Designing your JSON schema
The JSON schema you pass in OutputStructure.JsonSchemaDefinition is the
contract, xander will do its best to return data matching exactly that shape. A few
tips for designing schemas that work well:
-
Use
additionalProperties: falseon every object. It forces the model to stay within the fields you've defined and prevents it from making up extra keys. -
Mark fields as
requiredonly if they really must be present. Optional fields work fine, the model will omit them when the source document doesn't contain that information. -
Use
descriptionon every property. The descriptions are shown to the model and dramatically improve extraction accuracy. "Date the delivery was completed in ISO format" is much better than just"deliveryDate". -
Constrain enums and formats where possible. If a field is always one
of a known set, declare it as
enum. If a field is a date, declare"format": "date". The model uses these constraints during extraction. -
Reuse with
$defsfor repeated structures (e.g. if you extract addresses for both supplier and recipient, define anAddresstype once and reference it twice).
Multiple extractions on the same PDF
If you need to extract different aspects of the same PDF (e.g. header data, then line
items, then footer signatures), upload the PDF once via POST /pdfstore/upload
and call POST /prompt/pdf-informationextraction-structured multiple times
with different schemas, all referencing the same PdfId. Each extraction is
a separate billed call but you save the upload bandwidth.
Gotchas and tips
-
Schema as JSON-encoded string. Note that
JsonSchemaDefinitionin the request is a string containing the JSON schema, not a nested object. Stringify your schema before putting it into the request body. - Scanned vs digital PDFs. xander handles both, but text quality matters. A high-resolution scan from a cheap multifunction printer will extract worse than a digital-native PDF generated by an ERP. If extraction quality is poor and the source is a scan, check whether you can get the document from your supplier in a digital format upstream.
- Multilingual documents. Documents in German, French, Italian and English all work without configuration. If your prompt template is also written in the document's language, you'll get better results than mixing languages.
- Don't extract everything in one shot. A schema with 50 fields will produce worse results than three focused schemas of 15 fields each, especially on long documents. Decompose by section.
- Cost. PDF extraction is one of the more expensive call types because the entire document content goes to the model. For high-volume scenarios, consider a pre-filter (e.g. only run extraction on PDFs that match a known supplier).
Next
For workflows that mix PDFs and voice notes (e.g. a technician dictates a repair report and uploads photos of the printed warranty form), you can combine this recipe with Field Service Voice Intake by running them in parallel and merging the results in your application.