Agents that handle bills, receipts or expense reports keep running into the same problem: the invoice is a PDF, and the agent needs fields it can trust: who sent it, the invoice number, the totals, the line items. This post shows a small HTTP API that does that one job and charges per page, either in USDC over x402 or by card with a prepaid credit pack.
Full disclosure: I run this API, and this post was drafted with AI assistance and checked by hand.
One real call
Every caller gets one free call per route, so you can try it with no wallet and no signup. This is the exact request I ran while writing this post, on a public sample invoice from the open-source invoice2data test set:
curl -s -X POST https://freetier-agent-api.pages.dev/api/invoice-json \
-H 'Content-Type: application/json' \
-d '{"url":"https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/AzureInterior.pdf"}'
You can send a url (PDF, PNG, JPEG or WebP, max 8 MB), plain text, or file_base64 + mime_type in a POST body.
What came back (trimmed)
{
"invoice_number": { "value": "INV/2023/03/0008", "quote": "Invoice INV/2023/03/0008", "confidence": 0.95 },
"issue_date": { "value": "2023-03-20", "quote": "Invoice Date:03/20/2023", "confidence": 0.9 },
"currency": { "value": "USD", "quote": "$ 42.00", "confidence": 0.9 },
"totals": {
"subtotal": { "value": 262.9, "quote": "Subtotal $ 262.90", "confidence": 0.98 },
"vat_total": { "value": 16.94, "quote": "Tax 15% on $ 112.90 $ 16.94", "confidence": 0.98 },
"total": { "value": 279.84, "quote": "Total $ 279.84", "confidence": 0.98 }
},
"line_items": [
{ "description": "[FURN_7777] Office Chair", "quantity": 1, "unit_price": 70, "amount": 70, "vat_rate": 15,
"quote": "[FURN_7777] Office Chair 1.00 Units 70.00 0.00 15.00% $ 70.00", "confidence": 0.85 }
],
"checks": {
"subtotal_plus_vat_equals_total": true,
"line_items_sum_equals_subtotal_or_total": true,
"vat_breakdown_sum_equals_vat_total": true,
"due_date_not_before_issue_date": true
},
"overall_confidence": 0.94,
"pages": 1,
"billed_usd": 0.003
}
The parts I care most about as an agent builder:
- Every field carries the verbatim quote it came from. Your code (or a human) can check a value against the document in one string search.
- Confidence is mechanical, not vibes. It is higher when the quote is found verbatim in the document text and the value matches the quote.
-
Totals are checked arithmetically. The
checksblock tells you whether subtotal + VAT = total and whether the line items add up, so an agent can route anything that fails to a human.
It is not perfect, and the response says so. On this demo invoice (an Odoo template with two addresses near the top), the vendor and customer roles came out mixed up. The quotes make that easy to spot, but you should still check the fields that matter before you pay anything.
Paying per call with x402 (USDC on Base)
After the free call, the route answers 402 Payment Required with the price: $0.003 per page (max 10 pages per call). With @x402/fetch the payment is handled for you:
// npm i @x402/fetch @x402/evm viem
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY); // wallet with USDC on Base
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
});
const res = await fetchWithPayment("https://freetier-agent-api.pages.dev/api/invoice-json", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: "https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/AzureInterior.pdf" }),
});
console.log(await res.json());
Failed calls (a bad URL, a document it cannot read) are not charged.
Pay by card instead (no wallet)
If you don't want to hold USDC, there is a prepaid credit pack on Gumroad: Agent Extraction API - Call Pack (1,000 credits for $3, 5,000 for $12, 20,000 for $40). You get a license key and send it as a header:
curl -s -X POST https://freetier-agent-api.pages.dev/api/invoice-json \
-H 'X-License-Key: YOUR-LICENSE-KEY' \
-H 'Content-Type: application/json' \
-d '{"url":"https://raw.githubusercontent.com/invoice-x/invoice2data/master/tests/compare/AzureInterior.pdf"}'
One credit is $0.001, so an invoice page costs 3 credits. The X-Credits-Remaining response header shows your balance, and failed calls are not charged.
Limits
- LLM extraction can misread layouts; check important values (the quotes make that quick).
- Scanned PDFs need image input.
- Documents are processed in memory and not stored. Extraction runs on Cloudflare Workers AI, not a third-party LLM API.
- Max 10 pages per call.
Other routes on the same API
The same API (and the same card credits) also covers token-risk (ERC-20 risk flags with evidence), site-contacts (role emails and phones from a company site) and page-rag (web page to Markdown + RAG chunks). There is also an MCP endpoint at /mcp and an OpenAPI spec at /openapi.json.
The listing is verified on PayAPI Market: that means PayAPI Market made one settled $0.001 test payment to one route (on-chain receipt). It is not an audit.
Docs and the full schema: freetier-agent-api.pages.dev/products/invoice-json
Disclosure: this article was drafted with AI assistance; I run this API.
Top comments (0)