Extract invoice line items to JSON — the exact request and response shape
A copy-paste-ready JSON API contract for invoice line items using fields, values, path-keyed cells, review.flagged, and normalized numeric and date values.
If you want to extract invoice line items to JSON, the part that matters is the contract: how rows are declared, where business values are returned, and how a review item maps back to the page.
space ocr models line items as a field of type: "array" whose children describe one row. Extracted data is returned in data.values. Coordinates and verification metadata are separate in data.cells[path]; for example, line_items[0].amount. The human-review queue is data.review.flagged, and deterministic date and number parsing is returned in data.normalized without changing the printed strings in values.
For a higher-level walkthrough, see extract line items from invoices. For production ingestion, see the invoice data extraction API.
The request: line items as a type:"array" field
You call POST /ocr/fields with one image and a fields array. A scalar field (invoice number, total) is a { name, type } pair. Line items are one field whose type is "array" and whose children describe the shape of a single row — the engine then returns as many rows as it finds on the page.
Field names and params are camelCase (the snake_case aliases like image_type still work but are deprecated). The engine takes raster images — JPEG, PNG, GIF, BMP, TIFF, WebP — sent as a URL or as pure base64; there's no PDF parsing at the engine layer (the web app converts PDF pages to PNG before OCR). Language is auto-detected, so there's no language parameter to set.
{
"image": "https://example.com/invoice.jpg",
"imageType": "url",
"fields": [
{ "name": "invoice_no", "type": "string", "required": true,
"pattern": "^[A-Z0-9-]+$" },
{ "name": "invoice_date", "type": "date", "required": true },
{ "name": "total", "type": "number", "required": true, "min": 0 },
{
"name": "line_items",
"type": "array",
"children": [
{ "name": "description", "type": "string", "required": true },
{ "name": "quantity", "type": "integer", "min": 0 },
{ "name": "unit_price", "type": "number", "min": 0 },
{ "name": "amount", "type": "number", "min": 0 }
]
}
]
}Declare the fields you need, as above. If you are exploring an unfamiliar document, autoFields: true is an alternative; for a production integration, an explicit schema makes validation and downstream mappings predictable.
curl -s https://api.space-ocr.com/ocr/fields \
-H "Authorization: Bearer $SPACE_OCR_API_KEY" \
-H "Content-Type: application/json" \
-d @request.jsonThe response: values and evidence are separate
The response is { "status": "success", "data": { ... } }. Read extracted fields from data.values. Read a row's union coordinates from a path such as data.cells["line_items[0]"], and a child cell from data.cells["line_items[0].amount"].
boxis an axis-aligned{ xmin, ymin, xmax, ymax }rectangle on a 0–1000 normalized grid.quadis the four-point polygon that follows page rotation.verifiedandreviewdescribe the current verification verdict. A review object can also reflect declared rules such as required, pattern, or range checks.evidencecontains diagnostic signals such as printed text and match ratio. Match ratio is supporting evidence, not a product-level confidence threshold.data.review.flaggedis the review queue. Each item has apaththat directly addressesdata.cells[path].data.normalizedcontains deterministic parsed representations for date, number, and integer fields whiledata.valuespreserves the printed form.
Convert coordinates to pixels with box.xmin / 1000 * data.image.width and the corresponding height formula. Use data.image, not the original upload dimensions, because it reflects the frame used by the response.
{
"status": "success",
"data": {
"values": {
"invoice_no": "INV-2049",
"invoice_date": "2026/08/31",
"total": "4,286",
"line_items": [
{ "description": "96K調製豆乳", "quantity": "2", "unit_price": "316", "amount": "632" }
]
},
"normalized": {
"invoice_date": "2026-08-31",
"total": 4286,
"line_items": [
{ "quantity": 2, "unit_price": 316, "amount": 632 }
]
},
"cells": {
"line_items[0]": {
"box": { "xmin": 92, "ymin": 405, "xmax": 944, "ymax": 448 },
"quad": [{ "x": 92, "y": 405 }, { "x": 944, "y": 405 }, { "x": 944, "y": 448 }, { "x": 92, "y": 448 }],
"verified": true,
"review": null,
"evidence": { "source": "vision_symbol_match" }
},
"line_items[0].amount": {
"box": { "xmin": 860, "ymin": 412, "xmax": 944, "ymax": 446 },
"quad": [{ "x": 860, "y": 412 }, { "x": 944, "y": 412 }, { "x": 944, "y": 446 }, { "x": 860, "y": 446 }],
"verified": false,
"review": { "reasons": ["text_mismatch"] },
"evidence": { "text_match": false, "printed_text": "632", "match_ratio": 0.94 }
}
},
"review": {
"flagged": [{ "path": "line_items[0].amount", "reasons": ["text_mismatch"] }]
},
"image": { "width": 1600, "height": 2200 }
}
}Treat coordinates and OCR comparison as evidence, not proof. The response keeps the model's extracted value separate from the OCR pass's printed text and location. When signals or declared validation rules disagree, the path appears in data.review.flagged. Two systems can still agree on the same misread, so downstream arithmetic and business-rule checks remain useful.
Parsing it in code
Read rows from data["values"]["line_items"]. For a row index and child name, build a path such as line_items[0].amount and look it up in data["cells"]. Build the human-review list from data["review"]["flagged"]; do not invent a fixed match-ratio threshold.
import json, os, urllib.request
request_body = json.loads(open("request.json", encoding="utf-8").read())
req = urllib.request.Request(
"https://api.space-ocr.com/ocr/fields",
data=json.dumps(request_body).encode(),
headers={"Authorization": f"Bearer {os.environ['SPACE_OCR_API_KEY']}",
"Content-Type": "application/json"},
)
payload = json.load(urllib.request.urlopen(req))["data"]
values = payload.get("values", {})
cells = payload.get("cells", {})
normalized = payload.get("normalized", {})
flagged = {item["path"]: item.get("reasons", [])
for item in payload.get("review", {}).get("flagged", [])}
for i, row in enumerate(values.get("line_items", [])):
row_review = {}
for name in row:
path = f"line_items[{i}].{name}"
if path in flagged:
row_review[name] = {"reasons": flagged[path], "cell": cells.get(path)}
print({"values": row,
"normalized": normalized.get("line_items", [{}])[i],
"review": row_review})JavaScript uses the same paths: iterate data.values.line_items, form line_items[${i}].${name}, and look that path up in data.cells and data.review.flagged.
import csv
rows = values.get("line_items", [])
typed_rows = normalized.get("line_items", [])
invoice_no = values.get("invoice_no", "")
cols = ["description", "quantity", "unit_price", "amount"]
with open("line_items.csv", "w", encoding="utf-8-sig", newline="") as f:
writer = csv.writer(f)
writer.writerow(["invoice_no", *cols, "review_required"])
for i, row in enumerate(rows):
typed = typed_rows[i] if i < len(typed_rows) else {}
output = [row.get("description", ""), typed.get("quantity"),
typed.get("unit_price"), typed.get("amount")]
review_required = any(p.startswith(f"line_items[{i}]") for p in flagged)
writer.writerow([invoice_no, *output, review_required])Using utf-8-sig lets Excel recognize CJK text. Keep values for the printed representation and use normalized for arithmetic or database columns. Before export, compare quantity × unit price with amount and route any mismatch alongside the API's review queue. See scanned documents to CSV for stored-sheet workflows and the API docs for the complete response contract.
How to extract invoice line items to JSON
- Get an API keyCreate an API key and authenticate with Authorization: Bearer. Check the current pricing page for quota and export policy.
- Declare the row schemaUse fields with a line_items array and typed children. Add required, pattern, min, or other documented validation rules where the document contract supports them.
- Read values and normalized dataRead printed business values from data.values and typed number, integer, or date representations from data.normalized.
- Process the review queueIterate data.review.flagged and use each path to inspect the corresponding data.cells entry, including box, quad, review reasons, and evidence.
- Validate and exportRun arithmetic and business-rule checks, then write one UTF-8-BOM CSV row per line item. Use the current pricing page for CSV export charges.