# Bol.ai API documentation

> Source: <https://bol.ai/docs/>
> Published: 2026-06-14 00:00:00+00:00

Send a Bill of Lading document, receive structured JSON. One endpoint does the heavy lifting; two more let you retrieve past extractions and CSV exports.

`POST /api/bol/extract`

— body is the raw file (PDF, PNG or JPG, max 10 MB). The document type — Bill of Lading, commercial invoice, packing list or CMR — is detected automatically and returned as `doc_type`

. Each successful extraction bills one document (metered on subscriptions, or one prepaid credit), regardless of type.

```
curl -X POST https://bol.ai/api/bol/extract \
  -H "Authorization: Bearer bolai_YOUR_KEY" \
  -H "Content-Type: application/pdf" \
  -H "X-Filename: maersk-bl-4711.pdf" \
  --data-binary @maersk-bl-4711.pdf
```

Response (`200`

):

```
{
  "id": "0d7c2f86-9f3e-4a4b-8e9a-1c2d3e4f5a6b",
  "status": "done",
  "doc_type": "bill_of_lading",
  "doc_label": "Bill of Lading",
  "fields": {
    "doc_type": "bill_of_lading",
    "bl_number": "MAEU123456789",
    "booking_number": "610203040",
    "scac": "MAEU",
    "carrier": "Maersk Line",
    "vessel": "EMMA MAERSK",
    "voyage": "124W",
    "shipper":      { "name": "...", "address": "..." },
    "consignee":    { "name": "...", "address": "..." },
    "notify_party": { "name": "...", "address": "..." },
    "port_of_loading": "Rotterdam, NL",
    "port_of_discharge": "New York, US",
    "place_of_receipt": null,
    "place_of_delivery": null,
    "containers": [{
      "container_number": "MSKU1234565",
      "seal_number": "NL445566",
      "container_type": "40RF",
      "packages": "1100 cartons",
      "description": "Frozen dairy products",
      "gross_weight_kg": 21500,
      "volume_cbm": 58.4
    }],
    "freight_terms": "PREPAID",
    "incoterms": "CIF",
    "date_of_issue": "2026-05-28"
  }
}
```

Fields that cannot be read from the document are `null`

— they are never guessed.

When a container number fails its ISO 6346 check digit but a single unambiguous OCR-confusion fix passes, the corrected value is offered as `container_number_suggestion`

on that container (and echoed in `warnings`

). The read value is never overwritten.

One endpoint handles the freight documents that travel with a shipment. The type is classified automatically and returned as `doc_type`

(with a human `doc_label`

); `fields`

then carries the schema for that type. Each type has its own deterministic verification checks, and the CSV export adapts (one row per container, line item, package or goods line).

`doc_type` | Document | Key fields |
|---|---|---|
`bill_of_lading` | Bill of Lading | `bl_number` , carrier, vessel/voyage, ports, `containers[]` (ISO 6346 checked) |
`commercial_invoice` | Commercial / proforma invoice | `invoice_number` , seller, buyer, `line_items[]` , currency, `total_amount` |
`packing_list` | Packing list | `packing_list_number` , `packages[]` , net/gross weights, totals |
`cmr` | CMR road waybill | `cmr_number` , sender, consignee, carrier, `goods[]` , gross weight |

The full per-type schemas are in the [OpenAPI spec](/docs/openapi.json) (`BolFields`

, `InvoiceFields`

, `PackingListFields`

, `CmrFields`

). To pin extraction to a known type, omit it — classification is automatic; if a file is none of the supported types it is rejected and not billed.

`POST /api/bol/batch`

— `multipart/form-data`

with up to **10** files under the field `files`

. Each file is processed and billed independently; the response reports a per-file outcome, so a batch that runs out of credits part-way through still returns the documents that succeeded.

```
curl -X POST https://bol.ai/api/bol/batch \
  -H "Authorization: Bearer bolai_YOUR_KEY" \
  -F "files=@maersk-bl-4711.pdf" \
  -F "files=@msc-bl-8842.pdf"
{
  "results": [
    { "filename": "maersk-bl-4711.pdf", "ok": true,  "id": "0d7c…", "status": "done", "fields": { … }, "warnings": [] },
    { "filename": "msc-bl-8842.pdf",    "ok": false, "id": null,    "status": null,   "error": "payment required: …" }
  ]
}
```

For larger volumes, call the single `/extract`

endpoint concurrently, or forward BOLs to `extract@bol.ai`

(below). The web app accepts a multi-file drop and runs the same batch for you.

`GET /api/bol`

— your 100 most recent documents with status (`pending`

| `done`

| `failed`

).

```
curl https://bol.ai/api/bol -H "Authorization: Bearer bolai_YOUR_KEY"
```

`GET /api/bol/:id`

— full extraction result. `GET /api/bol/:id/csv`

— the same data as CSV (one row per container).

```
curl https://bol.ai/api/bol/0d7c2f86-.../csv \
  -H "Authorization: Bearer bolai_YOUR_KEY" -o extraction.csv
```

`PATCH /api/bol/:id`

— overwrite extracted fields with corrected values. Corrections are stored alongside the original output and used for CSV exports.

```
curl -X PATCH https://bol.ai/api/bol/0d7c2f86-... \
  -H "Authorization: Bearer bolai_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fields": { ...full corrected fields object... }}'
```

Turn a whole account's extractions into one master sheet. `GET /api/bol/export`

flattens every completed document into a normalized "shipment line" schema — one row per container, invoice line, package or goods line — so mixed document types land in a single file.

```
curl -H "Authorization: Bearer bolai_YOUR_KEY" \
  "https://bol.ai/api/bol/export?format=xlsx" -o shipments.xlsx
```

`format` | Returns |
|---|---|
`csv` (default) | Normalized master sheet, one row per line item |
`xlsx` | The same data as a real Excel workbook |
`json` | `{ count, rows: [...] }` — structured rows for a TMS/ERP integration |
`customs` | Customs-declaration draft from your Bills of Lading (HS code, parties, weights, container) |

Optional `?type=bill_of_lading|commercial_invoice|packing_list|cmr`

limits the export to one document type. `?template=<id>`

applies a saved **column template** — subset, reorder and rename columns to match your TMS/ERP import format. Manage templates in the app (Documents → *Columns…*) or via `GET/PUT /api/me/export-templates`

.

An export covers up to **1,000 documents** (newest first). Above that the response carries an `X-Export-Truncated: true`

header (and `truncated: true`

in the `json`

format) so you can page the rest via the API.

**Native connectors.** One-click Google Sheets and direct TMS push (CargoWise, Descartes, Magaya) are roadmap items — they require a Google OAuth app and each vendor's API credentials respectively. Until then, the `json`

export plus [webhooks](#webhooks) are the integration path: point a webhook at Zapier/Make/n8n/Sheets, or pull `format=json`

straight into your own backend.

Every account has a unique, private email-in address — `in-<token>@bol.ai`

, shown in the app under *Extract → Email documents in*. Forward or scan-to-email any freight document to it and the attachments (PDF/PNG/JPG) are extracted automatically and appear in your document list — same billing as uploads. Because the account is identified by the **address** rather than the sender, it works from any source: office scanners, shared mailboxes, auto-forward rules. Keep the address private (anyone with it can send into your account) and regenerate it if it leaks.

Legacy: `extract@bol.ai`

still works, matched by the sender address on your account.

Every extraction includes a `warnings`

array from a deterministic verification pass: ISO 6346 container check digits (`container_number_checksum_valid`

per container), weight and date plausibility, and Incoterms/freight-terms validation. An empty array means nothing suspicious was found — it is not a guarantee of correctness.

A shipment's documents must agree or the customs entry gets held. `GET /api/bol/:id/related`

links the other documents in the same shipment (by shared reference numbers — B/L number, invoice number) and reconciles the overlapping fields, returning each linked document with field-by-field `match`

/`mismatch`

results for shipper/seller, consignee/buyer, total gross weight (5% tolerance) and total package count. In the app, open any document to see the discrepancies inline. Deterministic — no extra usage is billed.

Set a webhook URL in the [app](/app/) (or `PUT /api/me/webhook`

with `{"url": "https://..."}`

) and every completed extraction — uploads, batch, API and email-in — is POSTed to it. That single primitive is how you connect Bol.ai to **Zapier, Make, n8n, a Google Sheet, or your own backend** without polling. Use *Send test event* in the app to fire a sample first.

Payload (`application/json`

, header `User-Agent: bol-ai-webhook/1.0`

, 5 s timeout, best-effort):

```
{
  "id": "0d7c2f86-…",
  "filename": "maersk-bl-4711.pdf",
  "status": "done",
  "fields": { …the full BolFields object… },
  "warnings": ["Container 1 (MSKU7234565): fails the ISO 6346 check digit — likely a misread… Did you mean MSKU1234565?"]
}
```

| Tool | How to receive it |
|---|---|
Zapier | New Zap → trigger Webhooks by Zapier → Catch Hook. Copy the generated URL into Bol.ai, click Send test event, then map `fields.*` to any action (Sheets, Airtable, email…). |
Make | Add a Custom webhook module, copy its address into Bol.ai, run Send test event so Make learns the data structure, then build the scenario. |
n8n | Start with a Webhook node (POST), paste its production URL into Bol.ai. |
Google Sheets | Publish a Google Apps Script `doPost(e)` web app that appends `JSON.parse(e.postData.contents).fields` as a row, then use its `/exec` URL as the webhook. |

Tip: include a hard-to-guess path or token in the URL (e.g. `https://you.example.com/bol/9f3a…`

) and reject anything else — that authenticates the caller without a shared secret.

Bol.ai is also a [Model Context Protocol](https://modelcontextprotocol.io) server, so Claude and other AI agents can extract BOLs directly. Endpoint: `https://bol.ai/api/mcp`

(streamable HTTP), authenticated with the same Bearer API key. Tools: `extract_bol`

(url or base64), `list_documents`

, `get_document`

.

```
{
  "mcpServers": {
    "bol-ai": {
      "type": "http",
      "url": "https://bol.ai/api/mcp",
      "headers": { "Authorization": "Bearer bolai_YOUR_KEY" }
    }
  }
}
```

| Status | Meaning |
|---|---|
`401` | Missing, invalid or revoked API key |
`402` | No active subscription or credits — subscribe or buy a credit pack |
`413` | File larger than 10 MB |
`422` | Extraction failed (unreadable document) — you are not billed; status is recorded as `failed` |

Documents and extracted data are stored exclusively in the European Union (database in Western Europe, file storage under EU jurisdiction).
