Invoice Line Items
Every accounts-payable workflow starts with the same tedious step: turning a supplier invoice into structured rows your ERP or bookkeeping tool can ingest. This prompt extracts only the line-item table — one object per printed row, with description, SKU, quantity, unit price, line total and tax rate — plus the invoice currency and the sum of the lines. It deliberately ignores the invoice header: no vendor name, invoice number, dates or grand total, so the output stays focused on what your import needs. That separation matters: most extraction failures come from mixing header totals with line totals. Watch out for multi-page invoices where the table continues after a page break, and for tax rates printed once in the header instead of per line — this schema expects a rate on every line, so use 0 when a line is untaxed rather than guessing. Pair it with Receipt Parser when you need the whole document.
System Prompt
You are a precise invoice line-item extraction engine. Read the provided invoice text and return ONLY a valid JSON object matching the schema below. Do not add explanations, markdown, or extra fields. Extract ONLY the line-item table of the invoice: one object per printed row, in printed order. Ignore the invoice header entirely: do not extract the vendor name, invoice number, issue date, or grand total. Every field is required. For each line, extract description, quantity, unit_price, line_total and tax_rate from the values printed on that row, and never invent a value that is not printed. Set sku to null when no SKU or product code is printed for that line. currency is the 3-letter uppercase ISO code of the invoice, for example USD, EUR or CAD. lines_total is the sum of the printed line_total values. Amounts are numbers with two decimals and no currency symbols. tax_rate is the decimal rate for the line, for example 0.2 for 20 percent; use 0 when a line is genuinely untaxed. If a line is missing a required value that cannot be found anywhere on the invoice, do not produce any output.
Raw JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InvoiceLineItems",
"type": "object",
"additionalProperties": false,
"required": ["currency", "line_items", "lines_total"],
"properties": {
"currency": {
"type": "string",
"minLength": 1,
"pattern": "^[A-Z]{3}$"
},
"line_items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["description", "sku", "quantity", "unit_price", "line_total", "tax_rate"],
"properties": {
"description": { "type": "string", "minLength": 1 },
"sku": { "type": ["string", "null"], "minLength": 1 },
"quantity": { "type": "number", "exclusiveMinimum": 0 },
"unit_price": { "type": "number", "minimum": 0 },
"line_total": { "type": "number", "minimum": 0 },
"tax_rate": { "type": "number", "minimum": 0 }
}
}
},
"lines_total": { "type": "number", "minimum": 0 }
}
}
Zod (TypeScript)
import { z } from "zod";
const LineItemSchema = z.object({
description: z.string().min(1),
sku: z.string().min(1).nullable(),
quantity: z.number().positive(),
unit_price: z.number().nonnegative(),
line_total: z.number().nonnegative(),
tax_rate: z.number().nonnegative(),
}).strict();
export const InvoiceLineItemsSchema = z.object({
currency: z.string().min(1).regex(/^[A-Z]{3}$/),
line_items: z.array(LineItemSchema).min(1),
lines_total: z.number().nonnegative(),
}).strict();
Pydantic v2 (Python)
from pydantic import BaseModel, ConfigDict, Field, field_validator
class LineItem(BaseModel):
model_config = ConfigDict(extra="forbid")
description: str = Field(..., min_length=1)
sku: str | None = Field(..., min_length=1)
quantity: float = Field(..., gt=0)
unit_price: float = Field(..., ge=0)
line_total: float = Field(..., ge=0)
tax_rate: float = Field(..., ge=0)
@field_validator("description", "sku", mode="before")
@classmethod
def _strip_text(cls, v):
if not isinstance(v, str):
return v
return v.strip()
class InvoiceLineItems(BaseModel):
model_config = ConfigDict(extra="forbid")
currency: str = Field(..., min_length=1, pattern=r"^[A-Z]{3}$")
line_items: list[LineItem] = Field(..., min_length=1)
lines_total: float = Field(..., ge=0)
@field_validator("currency", mode="before")
@classmethod
def _strip_currency(cls, v):
if not isinstance(v, str):
return v
return v.strip()