Home›Extracta›API
Extracta · API

Extracta API, as shipped in the download

Extracta REST API & Webhooks (v2.0)

Extracta ships a JSON REST API and signed webhooks so extracted invoice data can flow into your accounting system, a spreadsheet job, or whatever you already run — without anyone re-typing it.

Extracta uses your own LLM API key. Extracted data stays on your server; each document goes only to the provider you configure (with a local Ollama model, it never leaves your server).

Authentication

Create a key in API & Webhooks (in the sidebar). Send it on every call:

Authorization: Bearer apk_...

The key is shown once at creation and stored as a SHA-256 hash. Revoke any time.

Endpoints

Method & pathWhat it does
GET /api/pingVerify a key
GET /api/documents?status=&needs_review=1List documents
GET /api/documents/{id}One document with fields and line items
POST /api/documents/{id}/reviewApprove, optionally correcting fields first
GET /api/export?from=&to=&reviewed=1Extracted records in an invoice-date range
GET /api/openapi.jsonOpenAPI 3 spec (no auth)

Since 3.3.0 a document read from an e-invoice's XML also carries einvoice: its format, the seller's VAT ID, the buyer, the buyer reference, the account to pay, the amount due, the VAT breakdown per rate (category, rate, basis, tax) and the file's sha256. It is null for other documents.

Since 3.3.0 every document also carries recipient_status: the status recorded on the review page for a received invoice, as `{"status": "accepted" | "rejected" | "paid", "reported_on": "YYYY-MM-DD" | null, "reference": "...", "note": "..."}, or null` when none is recorded. It records what you reported through your platform; Extracta reports nothing itself. The MCP export_rows tool carries the same block as _recipient_status.

A key acts with the role of the person it belongs to: a viewer's key reads, and POST /api/documents/{id}/review answers it 403 with the permission it lacks (document.review).

Why upload is not in the API

Uploading is deliberately UI-only. A document arrives as bytes from a scanner, a phone camera or a mail rule, and it then needs a human to glance at the result. What an accounting system actually wants to talk to is everything after that — and that is what the API exposes. If you need unattended ingestion, turn on the email inbox (Settings → Email inbox, 3.2.0) and forward the documents to it; they arrive in the queue, and the API handles the rest of the pipeline.

"Needs review" is an arithmetic fact, not a guess

needs_review is not a confidence score invented for the API. It is Extraction::reconcile()'s verdict — the same check the review screen runs:

  • do the line items sum to the subtotal (or total)?
  • does subtotal + tax equal the total?

If either fails, needs_review is true and reconcile_warnings says exactly what did not add up. The API, the review screen and the email alert therefore can never disagree about whether a document is trustworthy. This matters more than a failed extraction: a failure is obvious, whereas a document that extracted "successfully" with numbers that do not reconcile will go into the books wrong unless somebody looks.

GET /api/documents?needs_review=1

is the endpoint to poll — extracted, not yet approved, and internally inconsistent.

Approve or correct

curl -X POST https://your-install/api/documents/42/review \
  -H "Authorization: Bearer apk_..." -H "Content-Type: application/json" \
  -d '{"fields": {"total": "1,234.56", "invoice_date": "07/03/2026"}}'

Only the fields you send are touched. Every value runs through the **same normalizers the review form uses** — money parsing (US and EU formats, symbols, negatives), date normalization honoring your day-first setting, and currency-code resolution — so a value posted here is stored exactly as the screen would have stored it. Send {"reviewed": false} to save corrections without approving. Since 3.3.0 a date sent with its time (2026-03-04T08:10:00Z) is stored as the date it shows (2026-03-04); before, it was stored as no date.

Approving promotes the document to the reviewed status, identically to the screen.

3.2.0: add a top-level "account" (for example {"account": "6100 Office supplies"}) to code the document for the accounting export. It is kept outside fields, and exactly as on the review screen, a changed account teaches the vendor rule. Every document object now also carries source (upload or email), account and account_source (rule, manual or empty), and a line item's unit_price is filled in (it was always null before 3.2.0).

Export

GET /api/export filters on the invoice date, not the upload date, because a bookkeeper closing July wants July's invoices however late they were scanned. reviewed=1 restricts it to documents a human has approved. Reviewed documents are always included — they are the ones most worth exporting.

Money is returned as both integer cents and the app's own decimal string, so nothing has to be re-derived downstream.

MCP — the agent surface (new in 3.0)

Extracta speaks MCP at POST /mcp, authenticated with the API key you already have:

Authorization: Bearer <your api key>

GET /mcp answers 405 with Allow: POST.

ToolWrites?What it does
list_documentsnoDocuments with status and reviewed flag
document_detailnoOne document, its fields and the arithmetic check
submit_documentYESRecords extracted values (whitelisted) and marks it reviewed; an optional account codes it (3.2.0)
review_decideYESApprove as-is, or send back to the queue
export_rowsnoThe extracted rows, ready for a ledger

Connect it to an assistant

Mint the key in the app first: Settings → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.

Every client needs the same three facts, and nothing in the handshake is vendor-specific:

The addresshttps://your-install/mcp
The keyheader Authorization: Bearer apk_xxxx
The transportMCP over streamable HTTP, stateless

Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:

claude mcp add --transport http extracta https://your-install/mcp \
  --header "Authorization: Bearer apk_xxxx"

ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):

{
  "type": "mcp",
  "server_label": "extracta",
  "server_url": "https://your-install/mcp",
  "authorization": "apk_xxxx",
  "require_approval": "never"
}

**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"*. Pasted as-is, a block written for another client will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.

A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:

curl -X POST https://your-install/mcp \
  -H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Extracta implements the protocol, not an integration with a particular vendor.

The whitelist is the product, and it applies to agents too

Extracta declares exactly eight fields — vendor, invoice_number, invoice_date, due_date, currency, subtotal_cents, tax_cents, total_cents — plus line items. **Anything else you send is dropped, not stored.** submit_document runs your fields object through the same builder the review form uses, which names every field it reads rather than copying keys, so an extra key has nowhere to go:

"fields": {"vendor": "Acme Ltd", "total": "120.00", "currency": "€",
           "bank_account": "IE29 …", "internal_note": "call the CFO"}

→ stored: vendor, invoice_number, invoice_date, due_date, currency (EUR),
          subtotal_cents, tax_cents, total_cents (12000), line_items
→ dropped: bank_account, internal_note

The response echoes stored_fields so you can see exactly what was kept. Line items narrow the same way: description, qty, unit_cents, amount_cents, nothing else.

Values also go through the product's own parsers, so "€" becomes EUR and "120.00" becomes 12000 cents — the same normalization a person typing into the review screen gets.

Reject does not delete

review_decide with "reject" clears the reviewed flag and returns the document to the queue. The extracted values are left alone — rejecting means "a person should look at this", not "throw the work away".

Arithmetic

document_detail returns reconcile, the product's own cross-check (line items sum to the subtotal; subtotal + tax equals the total). The API reports that verdict; it never re-adds anything itself.

Extraction templates (new in 3.0)

A template is a named subset of the declared fields for a document type — a utility bill has no line items, so its template hides them. A template can only ever narrow the set: the choices are exactly the declared fields, and the intersection is applied again when the template is read, so even a hand-edited row cannot introduce a field the extractor does not know.

Erasure (new in 3.0)

Extracta has no person table — a subject appears as a value inside an extracted record. So Subject search looks across the declared fields, the raw extraction, the sender and the file name (case-insensitively; the source documents themselves are not searched; 4.1.1) and Redact blanks the matching text values in place.

The amounts, the dates and the source document stay. The transaction did happen, and erasing its arithmetic would falsify the books rather than protect anyone. The audit entry is a count, and the search term itself is never written to it.

Webhooks

Add receiver URLs in API & Webhooks. Events:

  • document.extracted — fields have been pulled (payload carries needs_review)
  • document.reviewed — a human approved it

Both fire from every path, including the public-demo simulation and the real LLM call, so a receiver sees the same stream regardless of how the extraction ran.

Each delivery is an HTTP POST with a JSON body and:

X-Extracta-Event: document.extracted
X-Extracta-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the endpoint's secret>

Verify the signature before trusting a payload. Delivery is one attempt with a short timeout; the Recent deliveries log shows every attempt and its response code. Design receivers to be idempotent.

Email alerts

Set an SMTP server and a notification address in Settings. You get a mail when an extraction succeeds but does not reconcile — the case that would otherwise pass silently.

Backup

/backup.json exports every table with both the SMTP password and your LLM API key redacted. Extracta is the one product here that stores a third-party credential, and a backup file is exactly the sort of thing that ends up somewhere it should not.

Limits, honestly

  • The API acts at user level; there is no separate scope system.
  • List endpoints cap at 200 rows.
  • One delivery attempt per webhook event (log + idempotent receivers, not a retry queue).
  • Upload and re-extraction stay in the UI (see above); the email inbox is the unattended way in.
  • Extraction quality is your model's, not Extracta's. The reconcile check is the safety net, and it catches arithmetic — not a plausible-looking wrong vendor name.

Connect it to Zapier, Make or n8n

Extracta talks to other tools without a plugin: it sends a webhook (an HTTPS message) the moment something happens, and it accepts calls to the API above. Zapier, Make and n8n can use both.

Before you start

  • In Extracta, open API & Webhooks. Mint an API key (choose Read only if the other tool only reads), and add a webhook when you have its URL (below).
  • Use your install's full address, for example https://extracta.yourcompany.com.

When something happens in Extracta: send it to another tool

  1. In the other tool, create the trigger that gives you a URL to receive webhooks: - Zapier: the "Webhooks by Zapier" app, trigger Catch Hook (Webhooks by Zapier needs a paid Zapier plan). - Make: module Webhooks → Custom webhook. - n8n: the Webhook node.
  2. Copy that URL into Extracta under API & Webhooks, and choose the event or events below.
  3. Make the event happen once in Extracta, so the other tool receives a sample and you can map its fields.
EventFires when
document.extractedFields have been pulled (payload carries )
document.reviewedA human approved it

Every delivery carries X-Extracta-Signature, an HMAC-SHA256 signature over the raw body. Zapier and Make accept the delivery without checking it, so keep the webhook URL private. In n8n you can check it in a Code node before using the data.

When something happens elsewhere: act in Extracta

  • Zapier: "Webhooks by Zapier", action POST (or Custom Request).
  • Make: module HTTP → Make a request.
  • n8n: the HTTP Request node.

Send the request to your install's address plus the path from the Endpoints table above, with the header Authorization: Bearer <your API key> and a JSON body. A read-only key cannot write.

The endpoints that change something:

  • POST /api/documents/{id}/review: Approve, optionally correcting fields first

Example recipes

  • document.extracted → a row in Google Sheets: webhook trigger on document.extracted (Fields have been pulled (payload carries )), then the Google Sheets "Create row" action with the fields you want.
  • document.reviewed → a message in Slack or Teams: webhook trigger on document.reviewed (A human approved it), then the Slack or Microsoft Teams "Send message" action.

These recipes use only the events and endpoints documented above. We have not run them inside Zapier, Make or n8n ourselves; Extracta's own automated tests check its API.

← Back to Extracta · Manual · Quickstart · Test run