Skip to main content

HTTP API reference

Extraction

Run OCR + structured extraction on invoice files via URL or multipart upload.

Endpoints in this topic

POST/api/extract-invoice

Extract from a public URL (http or https); JSON body with file_url.

Request
Content-Type: application/json

{
  "file_url": "https://…",
  "invoice_kind": "expense" | "revenue"
}
(invoice_kind optional; defaults to expense)
Response
200 application/json. Body spreads InvoiceExtract (e.g.
  counterparty, invoice_number, date, total, subtotal, discount, tax,
  currency, items[], invoice_kind) plus optional meta:

{ source: "extracted"|"cache"|"duplicate", invoiceId?, duplicate? }.

POST/api/extract-invoice-file

Multipart upload: file + optional invoice_kind.

Request
Content-Type: multipart/form-data

- file: binary (required)
- invoice_kind: expense | revenue (optional field; default expense)
Response
Same 200 JSON shape as POST /api/extract-invoice (InvoiceExtract + optional meta).

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.

Extraction — API reference · BillflowAI