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, read and set document tags, generate exports, and upload new documents. Everything is JSON over HTTPS, scoped to a single account.
Prefer to ask in plain language? The same API is available inside Claude Code, Claude Desktop and Cursor through the official MCP server.
- Base URL
https://app.doxbox.io- Base path
/api/v1- Format
- JSON, UTF-8
- Access
- Read by default; uploading and changing tags require a
writekey
Quickstart
From zero to your first authenticated response in under a minute.
- Create a key. As an account admin, in the Doxbox web app on desktop: Settings → API & integrations → 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>
Keys are managed in Settings → API & integrations. The same screen lists your keys (created, last used) and lets you revoke one.
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 & tags, generate exports (default) |
write | Everything in read, plus uploading documents and changing document tags |
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 |
| 400 | TAG_LIMIT_PER_DOCUMENT | Document already has 10 tags |
| 404 | — | Not found, or not in your account |
| 404 | TAG_NOT_FOUND | Tag 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 · InvoiceReceipt · CreditInvoice · TransactionAccount · Other |
paymentStatus | Paid · Unpaid |
currency | ILS · USD · EUR · GBP · JPY · AUD |
tag color | teal · salmon · green · violet · sand · slate |
tag source | manual (a person) · rule (a supplier rule) · suggested_accepted · auto |
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 · InvoiceReceipt · CreditInvoice · TransactionAccount · 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 }. |
tags | array | [{ id, name, color, source }], in the order they were added. |
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 |
tags | string | Comma-separated tag ids, e.g. 7,12. Only documents carrying every listed tag (AND). Ids come from GET /tags. |
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", "tags": [{ "id": 7, "name": "Tax refund", "color": "teal", "source": "manual" }] }], "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 }], "tags": [{ "id": 7, "name": "Tax refund", "color": "teal", "source": "rule" }] }
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 "[email protected]" \ -F "[email protected]"
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.
Doxbox in Claude (MCP)
Use the API from Claude Code, Claude Desktop or Cursor, in plain language, through the official MCP server.
doxbox-mcp is an open-source (MIT) Model Context Protocol server that exposes this API as tools an AI assistant can call. Install nothing: MCP clients run it straight from npm. It talks only to app.doxbox.io, with the same per-account key as the REST API, and keeps no data of its own.
1. Create a key
As in the Quickstart: sign in as an account admin, go to Account settings → API and create a key. A read-only key covers searching, reading and exporting; create it as Read & write if you want the assistant to tag or upload documents.
2. Add it to your client
claude mcp add doxbox --env DOXBOX_API_KEY=dbx_... -- npx -y doxbox-mcp
{ "mcpServers": { "doxbox": { "command": "npx", "args": ["-y", "doxbox-mcp"], "env": { "DOXBOX_API_KEY": "dbx_..." } } } }
DOXBOX_BASE_URL is optional and defaults to https://app.doxbox.io. Node 18 or newer is required.
3. Ask
- "Which invoices from September are still unpaid?"
- "How much did I spend with Globex this year?"
- "Tag document 1042 with the Ramat Gan project."
- "Export July 2026 to Excel and give me the link."
Tools
| Tool | What it does | Key |
|---|---|---|
doxbox_health | API status and which server the MCP points at | none |
doxbox_list_documents | A page of documents, newest first (limit, cursor, tag_ids) | read |
doxbox_search_documents | Documents by supplier, date range, payment status, type, amount or tags; walks the pages and reports how far it scanned | read |
doxbox_get_document | One document: amounts, VAT, supplier with CRN, dates, tags, page ids | read |
doxbox_get_document_file_url | Short-lived signed URL for the original file | read |
doxbox_list_suppliers | Suppliers with document counts and totals, optional name filter | read |
doxbox_list_tags | The account's tags with document counts | read |
doxbox_create_export | Excel, CSV, merged PDF or ZIP of a month → download link | read |
doxbox_set_document_tags | Replace a document's tags | write |
doxbox_add_document_tag | Add one tag to a document | write |
doxbox_remove_document_tag | Remove one tag from a document | write |
doxbox_upload_documents | Upload local PDF or image files; OCR, supplier matching and de-duplication run as in the app | write |
Every tool answers in readable Markdown with the structured data alongside; response_format: "json" returns the full objects. Errors say what to do next (wrong key, read-only key, quota, rate limit).
The server is bound by the same rules as the API: one key is one account, a read-only key cannot change anything, and the rate limit applies (doxbox_search_documents spends one request per 100 documents scanned). Source and issues: github.com/wmgltd/doxbox-mcp.
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