Skip to content
Why is a field missing from the response?

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.

Last updated on