POST /api/extract-invoice
API documentation
This page documents POST /api/extract-invoice: JSON with a public file_url; the server downloads the file and returns structured invoice fields. To upload a file directly instead of hosting a URL, use POST /api/extract-invoice-file (multipart form). Authenticate with an dashboard API key or a session access token (same JWT the browser sends after sign-in). Other routes (saved invoices, BI, insights) are listed in the main API reference.
Endpoint
- Method
POST- Path
/api/extract-invoice- Full URL (production)
https://YOUR_APP_HOST/api/extract-invoice
Authentication
Every request must send a bearer token. The server accepts either:
- API key — create one in the dashboard; keys start with
bf_live_. Use the raw key string as the bearer token. - Session JWT — same
access_token the web app uses after sign-in.
Authorization: Bearer <YOUR_API_KEY_OR_ACCESS_TOKEN>
Send Content-Type: application/json with a JSON body (see below). Do not send form data for this route.
Request body
The JSON field file_url must point to a PDF or other supported format (including images when OCR applies). The server fetches the file; the URL must be publicly reachable and pass BillflowAI's URL safety checks.
{
"file_url": "https://example.com/invoices/invoice-2026-01.pdf",
"invoice_kind": "expense"
}Field file_url is required and must be a valid URL string. Optional invoice_kind: expense (default) or revenue.
Examples
cURL (API key)
Replace YOUR_HOST and YOUR_API_KEY.
curl -X POST "https://YOUR_HOST/api/extract-invoice" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"file_url":"https://cdn.example.com/docs/invoice.pdf","invoice_kind":"expense"}'cURL (session JWT)
curl -X POST "https://YOUR_HOST/api/extract-invoice" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"file_url":"https://example.com/invoice.pdf","invoice_kind":"expense"}'Node.js (fetch)
const res = await fetch("https://YOUR_HOST/api/extract-invoice", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BILLFLOW_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
file_url: "https://example.com/invoice.pdf",
invoice_kind: "expense",
}),
});
const data = await res.json();
if (!res.ok) {
throw new Error(JSON.stringify(data.error ?? data));
}
return data;Python (requests)
import os
import requests
r = requests.post(
"https://YOUR_HOST/api/extract-invoice",
headers={
"Authorization": f"Bearer {os.environ['BILLFLOW_API_KEY']}",
"Content-Type": "application/json",
},
json={
"file_url": "https://example.com/invoice.pdf",
"invoice_kind": "expense",
},
timeout=120,
)
r.raise_for_status()
print(r.json())Success response
200 OK with JSON. Fields may be null or omitted when the model cannot infer them; items defaults to an empty array. counterparty names the other party on the invoice. An optional meta object may be present (e.g. source: extracted | cache | duplicate, invoiceId) when the row was cached, deduplicated by invoice number, or newly stored.
{
"invoice_number": "INV-12345",
"date": "2026-03-20",
"counterparty": "Acme Corp",
"total": 1200,
"subtotal": 1300,
"discount": 100,
"tax": 180,
"currency": "USD",
"items": [
{ "description": "Service", "quantity": 1, "price": 1020, "line_total": null }
],
"meta": { "source": "extracted", "invoiceId": "uuid" }
}Error responses
Errors use JSON with an error object containing code and message (and sometimes validation details).
400 — 400 — VALIDATION (body not valid JSON or fails schema), or INVALID_URL (URL not allowed).401 — 401 — UNAUTHORIZED — missing or invalid bearer token.402 — 402 — QUOTA_EXCEEDED — plan quota used up.422 — 422 — FETCH_FAILED (could not download file) or EXTRACTION_FAILED (parse, OCR, or model error).500 — 500 — PERSIST_FAILED or other server errors when saving usage.
Example error body:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing or invalid credentials"
}
}Quotas
Free: successful extractions are limited per rolling window (default 30 days) and a small cap per account. Pro: monthly quota from your plan. Only successful pipeline runs that pass usage accounting count toward the quota.