Real World Example

Invoice and Receipt Extraction

Expense apps (Zoho Expense, Razorpay Payroll, Fyle) and GST tools read photos of bills and invoices: vision plus structured output. A ShopEase seller-onboarding version that reads a supplier invoice image:

import base64
from pydantic import BaseModel, Field
import anthropic

class LineItem(BaseModel):
    description: str
    quantity: int
    amount: float

class Invoice(BaseModel):
    seller_name: str
    gstin: str | None = Field(description="15-character GSTIN if printed, else null")
    invoice_number: str
    invoice_date: str = Field(description="YYYY-MM-DD")
    items: list[LineItem]
    total_amount: float

client = anthropic.Anthropic()
with open("invoice_photo.jpg", "rb") as f:
    img = base64.standard_b64encode(f.read()).decode("utf-8")

response = client.messages.parse(
    model="claude-sonnet-5-5",
    max_tokens=2000,
    messages=[{"role": "user", "content": [
        {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": img}},
        {"type": "text", "text": "Extract the invoice fields. Use null for anything not visible."},
    ]}],
    output_format=Invoice,
)
inv = response.parsed_output
assert abs(sum(i.amount for i in inv.items) - inv.total_amount) < 1, "totals do not match - send to review"
print(inv.model_dump_json(indent=2))

Run it

python invoice.py

Example output

{
  "seller_name": "Shree Ganesh Textiles",
  "gstin": "27AABCS1429B1Z5",
  "invoice_number": "SGT/2026/1187",
  "invoice_date": "2026-10-14",
  "items": [
    {"description": "Cotton kurta, indigo, M", "quantity": 20, "amount": 9800.0},
    {"description": "Cotton kurta, indigo, L", "quantity": 15, "amount": 7350.0}
  ],
  "total_amount": 17150.0
}

Checks Before Trusting It

Example

Model output from a photo can misread a digit. Cheap code checks catch many mistakes. Here the GSTIN format is checked with a regular expression and the line items are added up:

import re

GSTIN = re.compile(r"^\d{2}[A-Z]{5}\d{4}[A-Z][1-9A-Z]Z[0-9A-Z]$")

extracted = [
    {"gstin": "27AABCS1429B1Z5", "items": [9800.0, 7350.0], "total": 17150.0},
    {"gstin": "27AABCS1429B1Z5", "items": [9800.0, 7350.0], "total": 17750.0},   # misread 1 as 7
    {"gstin": "27AABCS142gB1Z5", "items": [5000.0], "total": 5000.0},             # misread 9 as g
]
for inv in extracted:
    problems = []
    if inv["gstin"] and not GSTIN.match(inv["gstin"]):
        problems.append("GSTIN format invalid")
    if abs(sum(inv["items"]) - inv["total"]) >= 1:
        problems.append(f"items add up to {sum(inv['items']):,.0f}, total says {inv['total']:,.0f}")
    print("OK" if not problems else "REVIEW: " + "; ".join(problems))

Output

OK
REVIEW: items add up to 17,150, total says 17,750
REVIEW: GSTIN format invalid