> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withboundary.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Invoice & Financial Extraction

> Validate invoice math before extracted financial data reaches accounting.

## The problem

Your invoice extraction prompt returns this:

```json theme={null}
{
  "vendor": "Acme Supplies",
  "invoiceNumber": "INV-1042",
  "lineItems": [
    { "description": "Hardware", "quantity": 2, "unitPrice": 50, "amount": 100 }
  ],
  "subtotal": 100,
  "tax": 10,
  "total": 105,
  "currency": "USD"
}
```

The JSON is valid. The arithmetic is not: `100 + 10 = 110`, not `105`. A schema can enforce numbers, but it cannot tell you that invoice totals agree.

## The contract

```typescript theme={null}
import { z } from "zod";
import { defineContract } from "@withboundary/contract";

const lineItemSchema = z.object({
  description: z.string(),
  quantity: z.number(),
  unitPrice: z.number(),
  amount: z.number(),
});

export const invoiceSchema = z.object({
  vendor: z.string(),
  invoiceNumber: z.string(),
  date: z.string(),
  lineItems: z.array(lineItemSchema),
  subtotal: z.number(),
  tax: z.number(),
  total: z.number(),
  currency: z.string(),
});

export const invoiceContract = defineContract({
  name: "invoice-extraction",
  schema: invoiceSchema,
  rules: [
    {
      name: "has_line_items",
      description: "Every invoice has at least one line item",
      fields: ["lineItems"],
      check: (invoice) =>
        invoice.lineItems.length > 0 || "invoice must include at least one line item",
    },
    {
      name: "line_items_sum_to_subtotal",
      description: "Line item amounts sum to subtotal",
      fields: ["lineItems", "subtotal"],
      check: (invoice) => {
        const sum = invoice.lineItems.reduce((total, item) => total + item.amount, 0);
        return Math.abs(sum - invoice.subtotal) < 0.01
          || `line items sum to ${sum}, but subtotal is ${invoice.subtotal}`;
      },
    },
    {
      name: "subtotal_plus_tax_equals_total",
      description: "Subtotal plus tax equals total within a cent",
      fields: ["subtotal", "tax", "total"],
      check: (invoice) => {
        const expected = invoice.subtotal + invoice.tax;
        return Math.abs(expected - invoice.total) < 0.01
          || `subtotal ${invoice.subtotal} plus tax ${invoice.tax} is ${expected}, but total is ${invoice.total}`;
      },
    },
    {
      name: "line_item_math_consistent",
      description: "Each line item amount equals quantity times unit price",
      fields: ["lineItems"],
      check: (invoice) => {
        const bad = invoice.lineItems.find(
          (item) => Math.abs(item.quantity * item.unitPrice - item.amount) >= 0.01,
        );

        return !bad
          || `${bad.description}: ${bad.quantity} x ${bad.unitPrice} should equal ${bad.quantity * bad.unitPrice}, but amount is ${bad.amount}`;
      },
    },
  ],
});
```

## Run the model

```typescript theme={null}
const result = await invoiceContract.accept(async (attempt) => {
  const response = await callYourLLM({
    messages: [
      {
        role: "user",
        content: [
          "Extract invoice data as JSON.",
          attempt.instructions,
          invoiceText,
        ].join("\n\n"),
      },
      ...attempt.repairs,
    ],
  });

  return response.text;
});
```

The repair loop is useful for extraction because the model often has enough source context to fix arithmetic or missing fields once the violation is explicit.

## Accept or reject

```typescript theme={null}
if (result.ok) {
  await accounting.createEntry(result.data);
  return { status: "accepted", invoice: result.data };
}

await invoiceReviewQueue.add({
  sourceDocumentId,
  reason: result.error.message,
  attempts: result.error.attempts,
});

return { status: "needs_review" };
```

Only accepted invoices should reach accounting. Rejected invoices still give you the attempt history, including which arithmetic rule failed and the values that caused it.

## When to use this pattern

* Invoice and receipt extraction
* Quote-to-order workflows
* Expense report validation
* Any AI-extracted financial record with totals, rates, or line items
