Introduction
v1The Doxbox API gives your account programmatic access to its financial documents — list and read invoices and receipts, fetch the original files, list suppliers, generate exports, and upload new documents. Everything is JSON over HTTPS, scoped to a single account.
- Base URL
https://app.doxbox.io- Base path
/api/v1- Format
- JSON, UTF-8
- Access
- Read by default; upload requires a
writekey
Quickstart
From zero to your first authenticated response in under a minute.
- Create a key. As an account admin: Account settings → Connections → API keys → Create key. Copy it — it's shown only once.
- Export it in your shell, then make the call:
# set once export DOXBOX_API_KEY="dbx_..." curl https://app.doxbox.io/api/v1/documents \ -H "Authorization: Bearer $DOXBOX_API_KEY"
const res = await fetch("https://app.doxbox.io/api/v1/documents", { headers: { Authorization: `Bearer ${process.env.DOXBOX_API_KEY}` } }); const { data, nextCursor } = await res.json();
import os, requests r = requests.get( "https://app.doxbox.io/api/v1/documents", headers={"Authorization": f"Bearer {os.environ['DOXBOX_API_KEY']}"}, ) docs = r.json()["data"]
Authentication
A per-account API key, sent as a Bearer token on every request except /health.
Authorization: Bearer dbx_<prefix>_<secret>
Scopes
Every key carries one of two permission levels, chosen at creation. Keys are read-only unless you opt in to write — calling a write endpoint with a read key returns 403 API_KEY_FORBIDDEN.
| Scope | Grants |
|---|---|
read | Read documents & suppliers, generate exports (default) |
write | Everything in read, plus uploading documents |
Conventions
The handful of rules that hold across every endpoint.
Pagination
List endpoints use cursor pagination: ?limit= (1–100, default 25) and ?cursor= (the last id from the previous page). Responses carry nextCursor — an id, or null on the last page.
Rate limiting
Requests are limited per key — default 120 / minute. Every response carries the headers below; exceeding the limit returns 429 with a Retry-After.
X-RateLimit-Limit: 120 X-RateLimit-Remaining: 118 X-RateLimit-Reset: 1785690000
Quotas
Uploads count against your plan's monthly document limit. At the limit, POST /documents returns 403 PLAN_LIMIT_EXCEEDED and no file in the batch is ingested — all-or-nothing, so you're never left with a partial upload.
Duplicate handling
Uploads are de-duplicated by file content. Re-uploading a file Doxbox already has doesn't create a second document — the response marks it "duplicate" and returns the existing id. Uploads are safe to retry.
Errors
A JSON body with a human message and, where useful, a machine-readable code.
{ "message": "API key has expired", "code": "API_KEY_EXPIRED" }
| HTTP | code | Meaning |
|---|---|---|
| 400 | — | Invalid request (bad parameters or body) |
| 401 | API_KEY_MISSING | No Authorization: Bearer header |
| 401 | API_KEY_INVALID | Unknown key |
| 401 | API_KEY_REVOKED | Key was revoked |
| 401 | API_KEY_EXPIRED | Key is past its expiry |
| 403 | API_KEY_FORBIDDEN | Key lacks the required scope (e.g. write) |
| 403 | PLAN_LIMIT_EXCEEDED | Monthly document quota reached |
| 404 | — | Not found, or not in your account |
| 429 | RATE_LIMITED | Rate limit exceeded (see Retry-After) |
Field reference
Enumerated values you'll see in responses.
documentType | Invoice · Receipt · CreditInvoice · Other |
paymentStatus | Paid · Unpaid |
currency | ILS · USD · EUR · GBP · JPY · AUD |
Amount fields are numbers in the document's currency. Dates are ISO 8601 strings, and may be null when Doxbox couldn't read them from the document.
The document object
Fields marked (detail) appear only on GET /documents/{id}; the list returns a lighter subset.
| Field | Type | Notes |
|---|---|---|
id | number | Stable document id. |
documentNumber | string | null | Invoice/receipt number, as read from the document. |
documentType | enum | Invoice · Receipt · CreditInvoice · Other. |
documentDate | ISO date | null | Date on the document. |
poNumber (detail) | string | null | Purchase-order number. |
netAmount · vat · vatRate · totalAmount | number | null | Amounts in currency; vatRate is a percentage. |
currency | enum | ILS · USD · EUR · GBP · JPY · AUD. |
paymentStatus | enum | Paid · Unpaid. |
paymentDate (detail) | ISO date | null | When it was marked paid. |
description (detail) | string | null | Free-text note. |
uploadedAt (detail) | ISO datetime | When it was added to Doxbox. |
supplierName / supplier (detail) | string / object | List returns the name; the detail endpoint returns { id, name, crn }. |
images (detail) | array | Page image ids [{ id }] — fetch via /documents/{id}/file?image=. |
Service health and version. The one endpoint that needs no key — handy for uptime checks.
{ "status": "ok", "api": "v1" }
List the account's documents, newest first. Cursor-paginated with limit and cursor.
| Query | Type | Notes |
|---|---|---|
limit | integer | 1–100, default 25 |
cursor | integer | The last document id from the previous page |
curl "https://app.doxbox.io/api/v1/documents?limit=2" \ -H "Authorization: Bearer $DOXBOX_API_KEY"
const res = await fetch("https://app.doxbox.io/api/v1/documents?limit=2", { headers: { Authorization: `Bearer ${key}` } });
r = requests.get( "https://app.doxbox.io/api/v1/documents", params={"limit": 2}, headers={"Authorization": f"Bearer {key}"}, )200 · application/json
{ "data": [{ "id": 1042, "documentNumber": "INV-2026-118", "totalAmount": 1170.0, "currency": "ILS", "documentType": "Invoice", "paymentStatus": "Unpaid", "supplierName": "Acme Ltd" }], "nextCursor": 1041 }
A single document with full metadata and the ids of its image pages. A document that isn't in your account returns 404 — no cross-account existence leak.
{ "id": 1042, "documentNumber": "INV-2026-118", "documentType": "Invoice", "netAmount": 1000.0, "vat": 170.0, "totalAmount": 1170.0, "currency": "ILS", "paymentStatus": "Unpaid", "supplier": { "id": 9, "name": "Acme Ltd", "crn": "514123456" }, "images": [{ "id": 5001 }, { "id": 5002 }] }
Returns a 302 redirect to a fresh, short-lived signed URL for the document's file — the storage bucket stays private. Optional ?image=<id> selects a specific page (defaults to the first).
curl -L https://app.doxbox.io/api/v1/documents/1042/file \ -H "Authorization: Bearer $DOXBOX_API_KEY" \ -o invoice.pdf
Upload one or more documents. They run through the same pipeline as documents added in the app — OCR, supplier matching, de-duplication — and appear tagged as uploaded via the API. Send multipart/form-data with one or more files in the files field. Counts against your monthly quota; the batch is all-or-nothing.
curl -X POST https://app.doxbox.io/api/v1/documents \ -H "Authorization: Bearer $DOXBOX_API_KEY" \ -F "files=@invoice-july.pdf" \ -F "files=@receipt-123.pdf"
const body = new FormData(); body.append("files", file1); body.append("files", file2); const res = await fetch("https://app.doxbox.io/api/v1/documents", { method: "POST", headers: { Authorization: `Bearer ${key}` }, body, });
files = [ ("files", open("invoice-july.pdf", "rb")), ("files", open("receipt-123.pdf", "rb")), ] r = requests.post( "https://app.doxbox.io/api/v1/documents", headers={"Authorization": f"Bearer {key}"}, files=files, )201 · created
{ "results": [ { "filename": "invoice-july.pdf", "documentId": 1055, "status": "created" }, { "filename": "receipt-123.pdf", "documentId": 980, "status": "duplicate" } ], "summary": { "total": 2, "created": 1, "duplicate": 1, "failed": 0 } }
Per-file status is created, duplicate, or failed — a failed file carries a reason and never stops the rest of the batch.
The account's suppliers (those with documents), with per-supplier aggregates, sorted by total amount.
{ "data": [ { "id": 8, "name": "Globex", "crn": "514000222", "documentCount": 12, "totalAmount": 18400.0 }, { "id": 9, "name": "Acme Ltd", "crn": "514123456", "documentCount": 5, "totalAmount": 5850.0 } ] }
Generate an export file for a month's documents. Returns a signed download URL valid ~12 hours. Exporting via the API never changes your documents.
| Field | Type | Notes |
|---|---|---|
exportType | string | excel_monthly · csv · pdf_merged · zip |
scope | string | all_results (default) or selected |
selectedDocumentIds | number[] | required when scope is selected |
filters.year | number | e.g. 2026 |
filters.month | number | 1–12 |
curl -X POST https://app.doxbox.io/api/v1/exports \ -H "Authorization: Bearer $DOXBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "exportType": "excel_monthly", "filters": { "year": 2026, "month": 7 } }'
const res = await fetch("https://app.doxbox.io/api/v1/exports", { method: "POST", headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json", }, body: JSON.stringify({ exportType: "excel_monthly", filters: { year: 2026, month: 7 }, }), });
r = requests.post( "https://app.doxbox.io/api/v1/exports", headers={"Authorization": f"Bearer {key}"}, json={ "exportType": "excel_monthly", "filters": {"year": 2026, "month": 7}, }, )200 · application/json
{ "fileName": "Acme_2026-07.xlsx", "fileUrl": "https://storage.googleapis.com/.../signed...", "totalDocuments": 17, "skippedFiles": 0, "provider": "server" }
fileUrl is a temporary signed URL, valid for about 12 hours.
Recipes
Common end-to-end flows, ready to adapt.
Paginate through every document
let cursor, all = []; do { const url = `https://app.doxbox.io/api/v1/documents?limit=100` + (cursor ? `&cursor=${cursor}` : ""); const { data, nextCursor } = await (await fetch(url, { headers })).json(); all.push(...data); cursor = nextCursor; } while (cursor); // nextCursor is null on the last page
Automate a monthly export
# create an Excel export for a month and download it url=$(curl -s -X POST https://app.doxbox.io/api/v1/exports \ -H "Authorization: Bearer $DOXBOX_API_KEY" -H "Content-Type: application/json" \ -d '{ "exportType": "excel_monthly", "filters": { "year": 2026, "month": 7 } }' \ | jq -r .fileUrl) curl -L "$url" -o export.xlsx # signed URL, ~12h
MCP server
Operate Doxbox from an AI client — Claude Desktop, Claude Code, Cursor — in plain language.
doxbox-mcp is a Model Context Protocol server that wraps this REST API as tools. Same per-account API key; every tool maps to an endpoint on this page. Ask your assistant to "list July's unpaid invoices", "upload this PDF", or "export last month to Excel".
Configure your client
// claude_desktop_config.json { "mcpServers": { "doxbox": { "command": "npx", "args": ["-y", "doxbox-mcp"], "env": { "DOXBOX_API_KEY": "dbx_..." } } } }
claude mcp add doxbox \ --env DOXBOX_API_KEY=dbx_... \ -- npx -y doxbox-mcp
Tools
| Tool | Maps to |
|---|---|
health | GET /health |
list_documents | GET /documents |
get_document | GET /documents/{id} |
get_document_file_url | GET /documents/{id}/file |
list_suppliers | GET /suppliers |
upload_documents | POST /documents · needs write |
create_export | POST /exports |
Read-safe by default — only upload_documents writes, and only with a write-scoped key. Source & setup: github.com/wmgltd/doxbox-mcp.