How to Build a Claude-Powered Data Extraction Tool
Building a Claude-powered data extraction tool means sending unstructured text — invoices, emails, contracts, resumes, support tickets — to Claude and getting back clean, structured data you can store in a database or feed into another system. The core pieces are: a well-defined output schema, a prompt (or tool definition) that forces Claude to follow it, validation on the response, and a pipeline that handles retries and edge cases.
This is one of the most common production use cases for LLMs because it replaces brittle regex and OCR post-processing with something that actually understands context — Claude can tell a shipping address from a billing address, or figure out that "net 30" means a 30-day payment term, without you writing a parser for every document variant.
Step 1: Define your extraction schema first
Before writing any prompt, decide exactly what fields you need and their types. This is the single biggest factor in extraction quality — vague schemas produce vague output.
{
"invoice_number": "string",
"invoice_date": "YYYY-MM-DD",
"vendor_name": "string",
"line_items": [
{ "description": "string", "quantity": "number", "unit_price": "number" }
],
"total_amount": "number",
"currency": "string (ISO 4217)"
}
Be explicit about formats (date format, decimal vs. string for money), nullable fields, and what to do when a field is missing. "Missing" is not an edge case — it's the normal case in real documents, so decide upfront whether Claude should return null, omit the key, or return an empty string.
Step 2: Use tool definitions for structured output
The most reliable way to get structured data out of Claude is tool use (sometimes called function calling). Instead of asking Claude to "return JSON" in plain text and hoping it doesn't add commentary, you define a tool whose input schema matches your extraction schema, and Claude returns a tool call with arguments that already conform to it.
{
"name": "extract_invoice",
"description": "Extract structured invoice data from raw document text",
"input_schema": {
"type": "object",
"properties": {
"invoice_number": { "type": "string" },
"invoice_date": { "type": "string" },
"vendor_name": { "type": "string" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"quantity": { "type": "number" },
"unit_price": { "type": "number" }
}
}
},
"total_amount": { "type": "number" }
},
"required": ["invoice_number", "total_amount"]
}
}
This approach is far more consistent than prompting for raw JSON because the schema constrains the model's output shape directly. If you're new to this pattern, the tool use docs walk through the request/response format in detail.
Step 3: Write the extraction prompt
Even with tool use, the surrounding prompt matters. Keep it short and specific:
- State the document type explicitly ("This is a scanned invoice; OCR may contain typos")
- Tell Claude how to handle ambiguity ("If currency is not stated, assume EUR")
- Tell Claude what to do with missing fields ("Use null, do not guess")
- Include one or two example inputs/outputs if your documents have unusual structure
Avoid asking Claude to "double-check" or "be extra careful" — it doesn't improve accuracy and just adds latency. Precision comes from schema and examples, not instructions about effort.
Step 4: Call the API and validate the response
Here's a minimal extraction call using SubToAPI, which exposes Claude through a standard HTTPS endpoint with an API key you generate in your dashboard:
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
tools: [extractInvoiceTool],
tool_choice: { type: "tool", name: "extract_invoice" },
messages: [
{ role: "user", content: `Extract fields from this invoice:\n\n${documentText}` }
]
})
});
const data = await res.json();
const extracted = data.content.find(b => b.type === "tool_use")?.input;
Never trust the output blindly, even with a forced schema. Run it through a validator (Zod, JSON Schema, Pydantic equivalents) before writing to your database. Validation catches cases where a number came back as a string, a date is malformed, or a required field is missing because the source document genuinely didn't have it.
Step 5: Handle real-world document mess
Production extraction tools fail not because of the model but because of the inputs:
- OCR noise: scanned PDFs introduce typos and broken line breaks. Tell Claude explicitly that the text came from OCR.
- Multi-page documents: chunk large documents and tell Claude which chunk is which page, or extract page-by-page and merge.
- Inconsistent layouts: don't assume every vendor's invoice looks the same. Test your prompt against at least 10–20 real samples before shipping.
- Low-confidence extractions: add a
confidencefield to your schema and route low-confidence results to a human review queue instead of auto-committing them.
Step 6: Scale and monitor usage
Once the extraction pipeline works for one document, the next problem is volume and cost. Running thousands of extraction calls a day means you need:
- Streaming or batching to control latency and throughput — see the streaming guide if you're processing documents one at a time in a UI
- Per-key usage tracking so you can see which part of your pipeline is driving spend
- Team access if multiple engineers are iterating on prompts and schemas
SubToAPI gives each application its own sub_live_ key with usage metadata per request, so you can isolate the extraction service from other Claude-powered features in your product and see exactly what it costs. Start with the quickstart to get a key, or check pricing if you're estimating cost across a Solo, Team, or Scale plan — all available with a free trial from signup.
Keep schemas versioned
As your extraction tool matures, your schema will change — new fields, renamed fields, new document types. Version your schemas (invoice_v1, invoice_v2) rather than mutating them in place, and keep old prompt/schema pairs around so you can re-run historical documents consistently if you ever need to backfill.
Questions
Can Claude extract data from scanned PDFs directly? Claude processes text and images depending on the model, but for scanned PDFs you typically run OCR first (or convert the PDF to images and use image input) and pass the resulting text to Claude for structuring. Pure text extraction from a raw PDF binary isn't something Claude does without a preprocessing step.
Should I use tool use or plain JSON prompting for extraction? Use tool use. It constrains the output to your schema at the API level, which is more reliable than asking Claude to emit raw JSON in a text response, especially as your schema grows more complex with nested objects and arrays.
How do I handle documents where required fields are missing? Define explicit behavior in your schema and prompt — return null rather than omitting the key or guessing a value. Then validate on your side and route anything with missing required fields to manual review instead of silently accepting incomplete data.