Why is a field missing from the response?
Because the response contains what the model found in that document and nothing else. A field
the document does not carry is left out of the object rather than set to null.
This invoice had a total and two named parties, so that is all that came back:
{
"invoice_number": "2026-014",
"customer": { "name": "Acme Corp." },
"seller": { "name": "unidoc.io" },
"total": { "amount": 1240.00, "currency": "USD" }
}No line_items, no taxes, no invoice_date. That is a normal successful response.
Required does not mean guaranteed
The invoice schema marks seller, customer, line_items and total as required, and the
resume schema marks name, contact, educations, work_experiences and skills. Those
constrain what the model is asked for, not what arrives. The response above is missing
line_items despite it being on that list.
Treat every field as optional. A client that indexes straight into the response will fail on the first document that happens to be missing something.
Common causes
The document genuinely does not have it. This is most cases. A receipt with a single total has no itemized rows to extract.
It is a scan. No text layer means nothing to extract, and the response comes back nearly
empty with a 200. Add is_scanned=true; see
scanned documents.
The other three are easier to spot once you know them. Posting a resume to /extract/invoice
returns an invoice-shaped object with nothing in it. A field past page three is not merely
missed, since only the first three pages can be sent at all and a longer PDF is rejected
outright, covered in what are the limits?. And two-column or heavily
designed documents extract in a reading order that can interleave the columns, which makes
labels hard to associate with their values.
Handling it
Decode into a type where absence is visible rather than indistinguishable from zero:
type Money struct {
Amount *float64 `json:"amount"`
Currency *string `json:"currency"`
}
type Invoice struct {
InvoiceNumber *string `json:"invoice_number"`
Total *Money `json:"total"`
LineItems []map[string]any `json:"line_items"`
}A nil Total means “not found”. A Total.Amount of 0 on a real invoice means something went
wrong and is worth surfacing to a human. Those are different and you want to tell them apart.
A truncated response looks the same
The body is streamed and the 200 is sent before generation starts, so a failure partway through arrives as a valid status with an incomplete body. That looks like a lot of missing fields.
Decoding is the test: if the JSON does not parse, you got a partial response rather than a sparse one, and retrying is reasonable. If it parses and fields are absent, the document did not have them.
Bound the retries. Credits are committed before generation starts, so the truncated attempt was already billed and each retry bills the pages again.