← Blog

How to Build a Claude-Powered Invoice Parser

2026-11-02 · 5 min read · SubToAPI Team

Parsing invoices used to mean brittle regex, OCR templates tied to a specific vendor layout, or expensive dedicated document-AI services. A Claude-powered invoice parser solves this differently: you send the invoice (image, PDF text, or scanned text) to Claude with a prompt that defines the exact JSON schema you want, and Claude returns structured fields — vendor name, invoice number, date, line items, totals, tax — regardless of layout. This works because Claude is good at reading unstructured documents and mapping them to a schema you define once, rather than one you maintain per vendor.

The rest of this guide walks through the practical steps: extracting text from the invoice, writing a prompt that reliably produces clean JSON, validating the output, and wiring it into a pipeline you can run in production.

Why Claude works well for this

Traditional invoice parsers rely on template matching or trained OCR models that break the moment a new vendor sends an invoice in a different layout. Claude doesn't need a template — it reads the document the way a human would and extracts meaning, not just text position. That makes it a good fit for:

The tradeoff is that you're relying on a language model's extraction, not a deterministic parser, so validation and confidence checks matter more than with rule-based systems.

Step 1: Get the invoice into text

Claude works on text and, depending on the integration, images. If you have scanned PDFs or photos of paper invoices, run them through an OCR step first (or send the image directly if your integration supports vision input), then pass the extracted text to Claude. If invoices arrive as digital PDFs with embedded text, extract the text layer directly — libraries like pdf-parse in Node or pdfplumber in Python work fine for this.

import pdf from "pdf-parse";
import fs from "fs";

const buffer = fs.readFileSync("invoice.pdf");
const { text } = await pdf(buffer);
// `text` now holds the raw invoice content, ready for Claude

Step 2: Define the schema and prompt

The quality of your output depends almost entirely on how specific your prompt is. Give Claude a fixed JSON schema, tell it to return nothing else, and list edge cases explicitly (missing fields, multiple line items, different currencies).

const systemPrompt = `You are an invoice data extraction engine. Given raw invoice text, extract the following fields and return ONLY valid JSON matching this schema:

{
  "vendor_name": string,
  "invoice_number": string,
  "invoice_date": string (ISO 8601),
  "due_date": string (ISO 8601) | null,
  "currency": string (ISO 4217 code),
  "line_items": [
    { "description": string, "quantity": number, "unit_price": number, "total": number }
  ],
  "subtotal": number,
  "tax": number,
  "total": number
}

If a field is missing from the invoice, set it to null. Do not include explanations, markdown, or any text outside the JSON object.`;

Step 3: Call Claude with the invoice text

Once you have the system prompt and the extracted invoice text, send a request. Here's an example using SubToAPI, which exposes Claude through a standard HTTPS API with an application API key — useful if you want to keep your invoice parser decoupled from account-level credentials and give it its own key, rate limits, and usage tracking.

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4",
    "max_tokens": 1024,
    "system": "You are an invoice data extraction engine...",
    "messages": [
      { "role": "user", "content": "Extract the invoice data from this text:\n\n'"$INVOICE_TEXT"'" }
    ]
  }'

For higher-volume pipelines, SubToAPI's usage metadata lets you track how many invoices you're processing and what it costs per document, which is useful when you're trying to justify (or price) an automation project internally. See /docs/messages for the full request format.

Step 4: Validate the output

Even with a strict schema prompt, you should validate the response before trusting it downstream. Parse the JSON, check required fields exist, and verify the math (do line items sum to the subtotal, does subtotal + tax equal total). If validation fails, retry once with a follow-up message pointing out the discrepancy — this is often enough to fix rounding or missed line items.

function validateInvoice(data) {
  const lineTotal = data.line_items.reduce((sum, item) => sum + item.total, 0);
  const subtotalMatches = Math.abs(lineTotal - data.subtotal) < 0.01;
  const totalMatches = Math.abs(data.subtotal + data.tax - data.total) < 0.01;
  return subtotalMatches && totalMatches;
}

This single check catches most extraction errors without needing a human in the loop for every invoice.

Step 5: Handle scale and edge cases

A few things to plan for before this goes into production:

If your invoice pipeline grows into something used by an accounting team rather than just a script you run locally, moving to a Team or Scale plan (see /pricing) gives each person or service their own API key under one billing account, which makes auditing who's processing what much easier than sharing a single key.

Putting it together

A minimal production pipeline looks like: extract text → send to Claude with a strict JSON schema prompt → validate the math → write to your database or accounting system → flag anything that fails validation for manual review. That last step matters — treat Claude's output as a strong first draft, not a guaranteed-correct record, especially early on while you're still testing prompt reliability against your actual invoice formats.

If you're just getting started, sign up at /signup, grab an API key, and test the prompt above against a handful of real invoices from different vendors before wiring it into anything that touches your books.

questions

Can Claude parse scanned/image invoices, not just digital PDFs? Yes, if your integration passes image input to Claude, but accuracy depends on scan quality. For best results, run noisy scans through OCR first and send the cleaned text rather than relying on image extraction alone.

How do I stop Claude from returning extra text around the JSON? Be explicit in the system prompt that the response must be JSON only, with no explanations or markdown. If it still adds stray text, wrap your parsing in a regex that extracts the first {...} block before calling JSON.parse.

Is this accurate enough to skip manual review entirely? For most use cases, no — treat it as automation that reduces manual entry, not eliminates it. Run the validation check described above and route failures to a human reviewer rather than trusting every extraction blindly.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →