{
  "markdown": "# doc-extract-api\n\nDocument-to-structured-JSON extraction API. Takes a PDF/PNG/JPEG and returns clean JSON.\nFour fixed document types (invoice, receipt, contract, resume) get a matched schema plus\ndomain-specific validation; `/extract/custom` accepts *any* JSON Schema, so a caller isn't\nlimited to those four types. Built for autonomous AI agents: no signup, no API key, no human\ncheckout — an agent discovers the price and pays per call via [x402](https://www.x402.org) v2\n(USDC), entirely over HTTP, and can find this service programmatically via the **x402 Bazaar**\ndiscovery catalog. Also published as an MCP server (`doc-extract-api-mcp` on npm, and on the\n[official MCP Registry](https://registry.modelcontextprotocol.io)) — but that's a *client* for the\nsame paid API, not a free alternative: every MCP tool call is a real x402 payment from a wallet\nyou supply, not a locally-run, unmetered extraction. There is no free path to this service.\n\n## Why x402, not Stripe\n\nStripe Checkout is built for humans — it collects a card through a UI, and its payment\nbutton actively resists non-trusted, programmatic (agent-driven) submission. x402 revives\nthe HTTP `402 Payment Required` status code: hit a paid endpoint with no payment, get back a\n`402` whose `PAYMENT-REQUIRED` header carries a complete, machine-readable manifest (price,\nnetwork, receiving address, asset, and — via the Bazaar extension — a full input/output\nschema with a real example). Retry with an `X-PAYMENT` header carrying a signed USDC\ntransfer authorization, and the request goes through. No account, no key, no human in the\nloop — the discovery step and the payment step are the same HTTP round trip.\n\nPayment is only **settled** after the extraction succeeds — `@x402/hono`'s middleware\nverifies the payment signature before running the handler, but only executes the actual\non-chain transfer if the handler returns a non-error status (confirmed: this project's\n`paymentMiddlewareFromConfig` call skips settlement whenever the wrapped route responds\n≥400). A failed extraction costs the caller nothing.\n\n## Discovery: the x402 Bazaar\n\nThe Bazaar (`GET {facilitator}/discovery/resources`) is a live, queryable catalog of x402\nservices — ~15k listings as of this writing, searchable by agent clients\n(`client.extensions.bazaar.search({ query: \"...\" })`). Getting listed isn't a submission\nform: a facilitator catalogs a resource automatically the first time it processes a real\npayment that echoes the route's declared `bazaar` extension. `src/lib/discovery.ts` declares\nthat extension for all four routes — full input schema (multipart form-data, a `file` field)\nand a real, verified output example — so once a route's first live mainnet payment lands, it\nbecomes findable in the catalog with enough detail for an agent to integrate correctly\nwithout reading any docs. As of this writing the `/extract/invoice` catalog entry is still\nshowing a stale pre-migration record rather than this rich content — see git history / ask\nfor status; `/extract/receipt`, `/extract/contract`, and `/extract/resume` haven't had a\nmainnet payment yet so aren't catalogued at all.\n\n**Positioning** (from surveying the live Bazaar before writing the listing copy): generic OCR\ncompetitors are free but return raw unvalidated text; generic structured-extraction\ncompetitors charge ~$0.10/call and require the caller to already have plain text, not a raw\nfile. This is the only listing found doing both — raw PDF/image in, a fixed validated schema\nout. Priced at $0.05/call (still half the nearest real competitor). See `src/lib/discovery.ts`\nfor the exact listing copy and each route's tags (`serviceName`/`tags`/`description`).\n\n## Architecture\n\n- **`schemas/`** — JSON Schema for `invoice`, `receipt`, `contract`, and `resume` extractions\n  (also the tool `input_schema` sent to Claude, so the model is forced into this exact shape).\n- **`src/lib/claude.ts`** — calls the Anthropic Messages API with the document as a native\n  `document`/`image` content block and a forced `tool_choice`, so the model can't return\n  anything but the schema.\n- **`src/lib/validate.ts`** — post-extraction sanity checks: line items sum to subtotal,\n  subtotal + tax (+ tip) ≈ total, dates are valid and not in the future, currency is a real\n  ISO 4217 code, low-confidence fields get flagged.\n- **`src/lib/extract.ts`** — orchestrates one extraction pass, validates, and — if validation\n  fails — retries once with a corrective prompt describing exactly what didn't add up. Never\n  fails silently; returns `validation_warnings` alongside the data either way.\n- **`src/lib/customSchema.ts`** / **`src/routes/customExtractHandler.ts`** — `/extract/custom`:\n  same Claude forced-tool-use engine, but the tool's `input_schema` is a JSON Schema the caller\n  supplies at request time instead of one of the four fixed schemas. There's no domain-specific\n  validator (no fixed domain to check against), so this returns schema-conformant output, not\n  fact-checked output — `validation_warnings` is always `[]`. The caller's schema is bounded\n  (≤6000 chars, depth ≤4, ≤30 properties per level, no `$ref`) and rejected with a 400 —\n  which costs nothing, since x402 only settles on a non-error response — before any model call.\n  Accepts either a `file` (binary PDF/image, same as the fixed routes) **or** `content` (raw\n  text/HTML the caller already has — e.g. an already-scraped web page, ≤100,000 chars) — exactly\n  one of the two, not both. The text/HTML path is labeled explicitly as data in the prompt sent to\n  Claude, not instructions, since it's arbitrary caller-supplied content and could otherwise be a\n  prompt-injection vector.\n- **`src/lib/facilitator.ts`** — picks the free default facilitator on testnet, or, on mainnet\n  (`X402_NETWORK = \"base\"`), Coinbase's authenticated CDP facilitator plus Mogami and PayAI for\n  additional Bazaar-equivalent discovery surface (CDP stays first/primary — earlier facilitators\n  get precedence for verify/settle); maps this project's human-readable network names to the\n  CAIP-2 identifiers x402 v2 uses (`eip155:8453` etc). **Important:** `src/index.ts` caches the\n  built payment middleware at module scope rather than rebuilding it per-request — with multiple\n  facilitators, an unhealthy one adds its full timeout to `initialize()`, and rebuilding per-request\n  meant paying that cost on *every* request (this broke the API entirely the first time\n  multi-facilitator was tried). Caching means only a cold isolate's first request pays that cost.\n- **`src/lib/discovery.ts`** — the Bazaar listing content: descriptions, tags, and discovery\n  extensions (schema + real example) for all four routes.\n- **`src/index.ts`** — Hono app on Cloudflare Workers. `@x402/hono`'s\n  `paymentMiddlewareFromConfig` guards `/extract/*`, built per-request from `Env` bindings\n  (Workers only exposes env vars inside a request, not at module load time).\n- **`src/routes/extractHandler.ts`** — pure extraction logic; payment has already been\n  verified by the time this runs.\n- **`mcp/server.ts`** — stdio MCP server exposing `extract_invoice`, `extract_receipt`,\n  `extract_contract`, `extract_resume`, and `extract_custom` tools. Each tool call signs and\n  sends a real x402 payment (via `@x402/fetch`'s `wrapFetchWithPayment`, same mechanism as\n  `scripts/test-payment.mjs`) from a wallet supplied via `WALLET_PRIVATE_KEY`, then calls the\n  live HTTP API over the network — it does not run extraction locally or call Claude directly.\n  No free path: a missing or unfunded wallet fails the same way it would calling the API\n  directly.\n\n## One deliberate deviation from the original spec\n\nThe spec assumed a PDF→image conversion step (pdf2image/poppler). Cloudflare Workers can't\nrun native binaries, and Claude's Messages API accepts PDFs natively as a `document` content\nblock — so PDFs are sent to Claude as-is. This removes a whole pipeline stage and a\ndependency that wouldn't run on Workers anyway.\n\n## Setup\n\n```bash\ncd \"doc-extract-api\"\nnpm install\n```\n\n### 1. Secrets\n\nLocal dev — copy `.dev.vars.example` to `.dev.vars` and fill in a real key (gitignored):\n\n```bash\ncp .dev.vars.example .dev.vars\n```\n\nProduction:\n\n```bash\nnpx wrangler secret put ANTHROPIC_API_KEY\n```\n\n### 2. Wallet — where payments land\n\nEdit `wrangler.toml`: `X402_PAY_TO_ADDRESS` must be a real wallet address you control before\nthis can receive actual payments. It's a public receiving address (safe to commit, like a\nbank account number) — never put a private key anywhere in this project.\n\n`X402_NETWORK` defaults to `base-sepolia` (testnet, free fake USDC from a faucet — good for\nverifying the whole flow with zero real money at risk).\n\n`X402_PRICE_PER_CALL` defaults to `$0.05`, applied to all four routes.\n\n### 2b. Going to mainnet (`X402_NETWORK = \"base\"`)\n\nThe free default facilitator (`x402.org/facilitator`, used automatically on testnet) only\nsupports testnet — mainnet requires an authenticated facilitator. This project is wired for\nCoinbase's CDP facilitator (`src/lib/facilitator.ts`):\n\n1. Create a [CDP Portal](https://portal.cdp.coinbase.com) account and a **Secret API Key**\n   under Project → API Keys.\n2. `npx wrangler secret put CDP_API_KEY_ID`\n3. `npx wrangler secret put CDP_API_KEY_SECRET`\n4. Set `X402_NETWORK = \"base\"` in `wrangler.toml` and redeploy.\n\nPricing: free for the first ~1,000 settled transactions/month, then $0.001 each — payment\nverification itself is always free. If `X402_NETWORK` is `\"base\"` and these secrets aren't\nset, requests will fail loudly at the facilitator-config step rather than silently\nmisconfiguring payments.\n\nGetting listed in the Bazaar requires at least one real mainnet payment to go through — the\nfacilitator catalogs the resource the first time it processes a payment that echoes the\n`bazaar` extension. Run `scripts/test-payment.mjs` with a real funded wallet once to trigger\nit (see below), then confirm with:\n\n```bash\ncurl -s \"https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources?limit=200\" \\\n  | grep -o 'doc-extract-api'\n```\n\n### 3. End-to-end payment test (`scripts/test-payment.mjs`)\n\nSimulates a real autonomous agent: generates/uses a throwaway wallet, pays via x402 v2, and\ncalls the live API. Fund a test wallet first — testnet USDC from a faucet (e.g.\n[faucet.circle.com](https://faucet.circle.com), Base Sepolia) for a free run, or a small\namount of real USDC on Base if `X402_NETWORK` is `\"base\"`:\n\n```bash\nnode scripts/gen-test-wallet.mjs        # prints a fresh throwaway private key + address\nTEST_WALLET_PRIVATE_KEY=0x... node scripts/test-payment.mjs\n```\n\nPrints the settlement details and the recipient's before/after USDC balance — confirmed\nworking end to end on both Base Sepolia (testnet) and Base mainnet with real USDC.\n\n### 4. Run locally\n\n```bash\nnpm run dev\n```\n\n```bash\ncurl -X POST http://localhost:8787/extract/invoice -F \"file=@/path/to/invoice.pdf\"\n# → 402 Payment Required; full payment + discovery manifest in the PAYMENT-REQUIRED header\n```\n\n`/extract/custom` takes a `schema` field (a JSON Schema string, `type: \"object\"`), an optional\n`instructions` field, and exactly one of `file` (binary) or `content` (raw text/HTML):\n\n```bash\ncurl -X POST http://localhost:8787/extract/custom \\\n  -F \"file=@/path/to/doc.pdf\" \\\n  -F 'schema={\"type\":\"object\",\"properties\":{\"order_id\":{\"type\":\"string\"},\"ship_date\":{\"type\":\"string\"}}}' \\\n  -F \"instructions=Dates in YYYY-MM-DD\"\n```\n\nOr from raw text/HTML you already have — no file needed:\n\n```bash\ncurl -X POST http://localhost:8787/extract/custom \\\n  -F \"content=<html><body><h1>Acme Corp</h1><p>Founded 1998, HQ in Austin, TX</p></body></html>\" \\\n  -F 'schema={\"type\":\"object\",\"properties\":{\"company\":{\"type\":\"string\"},\"founded\":{\"type\":\"number\"},\"hq\":{\"type\":\"string\"}}}'\n```\n\nAn actual caller uses an x402-aware HTTP client (e.g. `@x402/fetch` on the agent's side) that\nhandles the 402 → sign payment → retry loop automatically.\n\n### 5. Deploy\n\n```bash\nnpm run deploy\n```\n\n### 6. MCP server (paid, agent use)\n\n```bash\nexport WALLET_PRIVATE_KEY=0x...   # a wallet funded with real USDC on Base — every call spends from it\nnpm run mcp\n```\n\nOr, once published, an MCP client can run it directly without cloning this repo:\n\n```bash\nnpx doc-extract-api-mcp\n```\n\nPoint an MCP-capable client at this stdio server to expose `extract_invoice`, `extract_receipt`,\n`extract_contract`, `extract_resume`, and `extract_custom` as tools. Every call pays the live API\nover the network via x402 — there is no local/free extraction path, and no `ANTHROPIC_API_KEY` is\nneeded on the client side at all (the server holds that; callers only need a funded wallet).\n\n## Not yet done\n\n- Resumes carry real PII (name, contact info, work history). Nothing is persisted after the\n  response is returned (same as every other document type here), but that's worth restating\n  given the sensitivity — see `src/lib/discovery.ts`'s resume description.\n",
  "bytes": 12930,
  "sha": "898dcec27a69e63130f85e8476ed89affff5a8135c761dffa0b736d24e6443ef",
  "repo_slug": "thestarboy9696/doc-extract-api",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_thestarboy9696_doc_extract_api_96c028a9/readme"
}