{
  "openapi": "3.1.0",
  "info": {
    "title": "Bol.ai API",
    "version": "1.0.0",
    "description": "Freight-document data extraction. Upload a Bill of Lading, commercial invoice, packing list or CMR road waybill (PDF/PNG/JPG); the document type is detected automatically and returned as doc_type, with a field set tailored to that type. Fields not present in the document are null — never guessed. One document = one usage unit regardless of type. Documents and data are stored exclusively in the EU.",
    "contact": { "url": "https://bol.ai" }
  },
  "servers": [{ "url": "https://bol.ai" }],
  "security": [{ "apiKey": [] }],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bol.ai API key (bolai_...). Create one in the app: https://bol.ai/app/ — available on the Professional plan or with credits; new accounts include 5 free documents."
      }
    },
    "schemas": {
      "Party": {
        "type": "object",
        "properties": {
          "name": { "type": ["string", "null"] },
          "address": { "type": ["string", "null"] }
        }
      },
      "Container": {
        "type": "object",
        "properties": {
          "container_number": { "type": ["string", "null"] },
          "seal_number": { "type": ["string", "null"] },
          "container_type": { "type": ["string", "null"] },
          "packages": { "type": ["number", "null"] },
          "package_type": { "type": ["string", "null"] },
          "description": { "type": ["string", "null"] },
          "gross_weight_kg": { "type": ["number", "null"] },
          "volume_cbm": { "type": ["number", "null"] },
          "container_number_checksum_valid": { "type": ["boolean", "null"], "description": "ISO 6346 check-digit result; false signals a likely OCR misread" },
          "container_number_suggestion": { "type": ["string", "null"], "description": "When the number fails its checksum but a single OCR-confusion fix passes, the corrected number; null otherwise. The read value is never overwritten." }
        }
      },
      "BolFields": {
        "type": "object",
        "properties": {
          "doc_type": { "type": "string", "const": "bill_of_lading" },
          "bl_number": { "type": ["string", "null"] },
          "booking_number": { "type": ["string", "null"] },
          "scac": { "type": ["string", "null"] },
          "carrier": { "type": ["string", "null"] },
          "vessel": { "type": ["string", "null"] },
          "voyage": { "type": ["string", "null"] },
          "shipper": { "$ref": "#/components/schemas/Party" },
          "consignee": { "$ref": "#/components/schemas/Party" },
          "notify_party": { "$ref": "#/components/schemas/Party" },
          "port_of_loading": { "type": ["string", "null"] },
          "port_of_discharge": { "type": ["string", "null"] },
          "place_of_receipt": { "type": ["string", "null"] },
          "place_of_delivery": { "type": ["string", "null"] },
          "containers": { "type": "array", "items": { "$ref": "#/components/schemas/Container" } },
          "hs_codes": { "type": "array", "items": { "type": "string" } },
          "freight_terms": { "type": ["string", "null"], "description": "PREPAID or COLLECT" },
          "incoterms": { "type": ["string", "null"] },
          "date_of_issue": { "type": ["string", "null"], "description": "YYYY-MM-DD" },
          "shipped_on_board_date": { "type": ["string", "null"] },
          "number_of_originals": { "type": ["number", "null"] }
        }
      },
      "InvoiceLineItem": {
        "type": "object",
        "properties": {
          "description": { "type": ["string", "null"] },
          "hs_code": { "type": ["string", "null"] },
          "quantity": { "type": ["number", "null"] },
          "unit": { "type": ["string", "null"] },
          "unit_price": { "type": ["number", "null"] },
          "amount": { "type": ["number", "null"] }
        }
      },
      "InvoiceFields": {
        "type": "object",
        "properties": {
          "doc_type": { "type": "string", "const": "commercial_invoice" },
          "invoice_number": { "type": ["string", "null"] },
          "invoice_date": { "type": ["string", "null"], "description": "YYYY-MM-DD" },
          "seller": { "$ref": "#/components/schemas/Party" },
          "buyer": { "$ref": "#/components/schemas/Party" },
          "incoterms": { "type": ["string", "null"] },
          "currency": { "type": ["string", "null"], "description": "3-letter ISO code" },
          "country_of_origin": { "type": ["string", "null"] },
          "country_of_destination": { "type": ["string", "null"] },
          "payment_terms": { "type": ["string", "null"] },
          "related_bl_number": { "type": ["string", "null"] },
          "line_items": { "type": "array", "items": { "$ref": "#/components/schemas/InvoiceLineItem" } },
          "subtotal": { "type": ["number", "null"] },
          "total_amount": { "type": ["number", "null"] }
        }
      },
      "PackingListPackage": {
        "type": "object",
        "properties": {
          "marks": { "type": ["string", "null"] },
          "description": { "type": ["string", "null"] },
          "quantity": { "type": ["number", "null"] },
          "package_type": { "type": ["string", "null"] },
          "net_weight_kg": { "type": ["number", "null"] },
          "gross_weight_kg": { "type": ["number", "null"] },
          "dimensions": { "type": ["string", "null"] }
        }
      },
      "PackingListFields": {
        "type": "object",
        "properties": {
          "doc_type": { "type": "string", "const": "packing_list" },
          "packing_list_number": { "type": ["string", "null"] },
          "date": { "type": ["string", "null"], "description": "YYYY-MM-DD" },
          "shipper": { "$ref": "#/components/schemas/Party" },
          "consignee": { "$ref": "#/components/schemas/Party" },
          "related_invoice_number": { "type": ["string", "null"] },
          "related_bl_number": { "type": ["string", "null"] },
          "total_packages": { "type": ["number", "null"] },
          "total_gross_weight_kg": { "type": ["number", "null"] },
          "total_net_weight_kg": { "type": ["number", "null"] },
          "total_volume_cbm": { "type": ["number", "null"] },
          "packages": { "type": "array", "items": { "$ref": "#/components/schemas/PackingListPackage" } }
        }
      },
      "CmrGoods": {
        "type": "object",
        "properties": {
          "marks": { "type": ["string", "null"] },
          "number_of_packages": { "type": ["number", "null"] },
          "packing_method": { "type": ["string", "null"] },
          "nature_of_goods": { "type": ["string", "null"] },
          "gross_weight_kg": { "type": ["number", "null"] },
          "volume_cbm": { "type": ["number", "null"] }
        }
      },
      "CmrFields": {
        "type": "object",
        "properties": {
          "doc_type": { "type": "string", "const": "cmr" },
          "cmr_number": { "type": ["string", "null"] },
          "place_of_issue": { "type": ["string", "null"] },
          "date_of_issue": { "type": ["string", "null"], "description": "YYYY-MM-DD" },
          "sender": { "$ref": "#/components/schemas/Party" },
          "consignee": { "$ref": "#/components/schemas/Party" },
          "carrier": { "$ref": "#/components/schemas/Party" },
          "place_of_taking_over": { "type": ["string", "null"] },
          "place_of_delivery": { "type": ["string", "null"] },
          "vehicle_registration": { "type": ["string", "null"] },
          "goods": { "type": "array", "items": { "$ref": "#/components/schemas/CmrGoods" } },
          "total_gross_weight_kg": { "type": ["number", "null"] }
        }
      },
      "ExtractedDocument": {
        "oneOf": [
          { "$ref": "#/components/schemas/BolFields" },
          { "$ref": "#/components/schemas/InvoiceFields" },
          { "$ref": "#/components/schemas/PackingListFields" },
          { "$ref": "#/components/schemas/CmrFields" }
        ],
        "discriminator": { "propertyName": "doc_type" },
        "description": "One of the four freight-document field sets, distinguished by doc_type."
      },
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" } }
      }
    }
  },
  "paths": {
    "/api/bol/extract": {
      "post": {
        "summary": "Extract a freight document",
        "description": "Body is the raw file (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 (Stripe meter on subscriptions, otherwise one prepaid credit). Failed extractions are not billed.",
        "parameters": [
          {
            "name": "X-Filename",
            "in": "header",
            "schema": { "type": "string" },
            "description": "Original filename incl. extension (default bol.pdf)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/pdf": { "schema": { "type": "string", "format": "binary" } },
            "image/png": { "schema": { "type": "string", "format": "binary" } },
            "image/jpeg": { "schema": { "type": "string", "format": "binary" } }
          }
        },
        "responses": {
          "200": {
            "description": "Extraction result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string" },
                    "status": { "type": "string", "enum": ["done"] },
                    "doc_type": { "type": "string", "enum": ["bill_of_lading", "commercial_invoice", "packing_list", "cmr"] },
                    "doc_label": { "type": "string", "description": "Human label for doc_type, e.g. \"Bill of Lading\"" },
                    "fields": { "$ref": "#/components/schemas/ExtractedDocument" },
                    "warnings": { "type": "array", "items": { "type": "string" }, "description": "Deterministic verification warnings tailored to the document type" }
                  }
                }
              }
            }
          },
          "401": { "description": "Missing/invalid API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "402": { "description": "No active subscription or credits", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "File larger than 10 MB", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "Extraction failed (not billed)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/bol/batch": {
      "post": {
        "summary": "Extract several freight documents in one call",
        "description": "multipart/form-data with up to 10 files under the field 'files'. Each file is processed, type-detected and billed independently; the response reports a per-file outcome so partial success is explicit. Failed or unbilled files have ok=false.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "files": { "type": "array", "items": { "type": "string", "format": "binary" }, "maxItems": 10 }
                },
                "required": ["files"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-file outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "filename": { "type": "string" },
                          "ok": { "type": "boolean" },
                          "id": { "type": ["string", "null"] },
                          "status": { "type": ["string", "null"], "enum": ["done", "failed", null] },
                          "doc_type": { "type": "string", "enum": ["bill_of_lading", "commercial_invoice", "packing_list", "cmr"] },
                          "doc_label": { "type": "string" },
                          "fields": { "$ref": "#/components/schemas/ExtractedDocument" },
                          "warnings": { "type": "array", "items": { "type": "string" } },
                          "error": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "description": "No files, or more than 10", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Missing/invalid API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/bol": {
      "get": {
        "summary": "List documents",
        "description": "The 100 most recent documents for this account.",
        "responses": {
          "200": {
            "description": "Document list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "documents": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": { "type": "string" },
                          "filename": { "type": "string" },
                          "doc_type": { "type": "string", "enum": ["bill_of_lading", "commercial_invoice", "packing_list", "cmr"] },
                          "bl_number": { "type": ["string", "null"], "description": "Primary reference (B/L, invoice, packing-list or CMR number) for list display" },
                          "status": { "type": "string", "enum": ["pending", "done", "failed"] },
                          "created_at": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/bol/{id}": {
      "get": {
        "summary": "Get one extraction",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Extraction with original and corrected fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string" },
                    "filename": { "type": "string" },
                    "status": { "type": "string" },
                    "error": { "type": ["string", "null"] },
                    "doc_type": { "type": ["string", "null"], "enum": ["bill_of_lading", "commercial_invoice", "packing_list", "cmr", null] },
                    "doc_label": { "type": ["string", "null"] },
                    "fields": { "oneOf": [{ "$ref": "#/components/schemas/ExtractedDocument" }, { "type": "null" }] },
                    "corrected_fields": { "oneOf": [{ "$ref": "#/components/schemas/ExtractedDocument" }, { "type": "null" }] },
                    "warnings": { "type": "array", "items": { "type": "string" } }
                  }
                }
              }
            }
          },
          "404": { "description": "Not found" }
        }
      },
      "patch": {
        "summary": "Save corrections",
        "description": "Store corrected fields alongside the original extraction. CSV exports use corrected data when present.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["fields"],
                "properties": { "fields": { "$ref": "#/components/schemas/ExtractedDocument" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Saved" },
          "404": { "description": "Not found" },
          "409": { "description": "Document has no extraction to correct" }
        }
      },
      "delete": {
        "summary": "Delete a document",
        "description": "Permanently removes the original file (R2) and the extraction (D1). Self-serve fulfilment of the per-document deletion right.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Deleted" },
          "404": { "description": "Not found" }
        }
      }
    },
    "/api/bol/{id}/csv": {
      "get": {
        "summary": "Download extraction as CSV",
        "description": "One row per line item (container, invoice line, package or goods line, depending on doc_type). Uses corrected fields when present.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "CSV file", "content": { "text/csv": { "schema": { "type": "string" } } } },
          "404": { "description": "Not found" }
        }
      }
    },
    "/api/bol/{id}/related": {
      "get": {
        "summary": "Cross-document reconciliation",
        "description": "Finds the other documents in this shipment (linked by shared reference numbers such as B/L number and invoice number) and compares them field by field: shipper/seller and consignee/buyer names, total gross weight (5% tolerance), package counts. Deterministic — no extra document billed.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": {
            "description": "Linked documents with per-field match/mismatch checks; empty array when the document is not done or nothing links to it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "related": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": { "type": "string" },
                          "filename": { "type": "string" },
                          "doc_type": { "type": "string" },
                          "checks": { "type": "array", "items": { "type": "object" } }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/bol/export": {
      "get": {
        "summary": "Bulk export all completed documents",
        "description": "Flattens every completed document (up to 1000, newest first) into normalized shipment-line rows — one row per container / invoice line / package / goods line. Corrected data wins. When more than 1000 documents exist the response carries x-export-truncated: true and x-export-row-cap headers.",
        "parameters": [
          { "name": "format", "in": "query", "schema": { "type": "string", "enum": ["csv", "xlsx", "json", "customs"], "default": "csv" }, "description": "csv = normalized master sheet; xlsx = the same as real Excel; json = TMS/ERP-ready rows; customs = customs-declaration draft from Bills of Lading" },
          { "name": "type", "in": "query", "schema": { "type": "string", "enum": ["bill_of_lading", "commercial_invoice", "packing_list", "cmr"] }, "description": "Only include documents of this type" },
          { "name": "template", "in": "query", "schema": { "type": "string" }, "description": "Id of a saved column template (see /api/me/export-templates); applies to csv and xlsx" }
        ],
        "responses": {
          "200": { "description": "The export. json format returns { count, truncated, row_cap, rows }; other formats return a file attachment." }
        }
      }
    },
    "/api/usage": {
      "get": {
        "summary": "Usage and billing state",
        "description": "Remaining prepaid credits, active subscription (if any) and can_extract. Agents should call this before large batches instead of discovering exhaustion via a 402 mid-batch. A 402 from extract endpoints includes buy_credits_url and checkout_endpoint so a human can top up.",
        "responses": {
          "200": {
            "description": "Usage state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "credits": { "type": "integer" },
                    "subscription": { "type": ["object", "null"], "properties": { "status": { "type": "string" }, "plan": { "type": "string", "enum": ["starter", "professional"] }, "current_period_end": { "type": ["string", "null"] } } },
                    "can_extract": { "type": "boolean" },
                    "buy_credits_url": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/me/webhook": {
      "put": {
        "summary": "Set or clear the webhook URL",
        "description": "Every completed extraction (upload, batch, API, email-in) is POSTed to this https URL as { id, filename, doc_type, doc_label, status, fields, warnings }. Clear by sending an empty url. Test delivery with POST /api/me/webhook/test.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "url": { "type": ["string", "null"], "description": "https:// URL, or empty/null to clear" } } } } }
        },
        "responses": {
          "200": { "description": "{ ok: true, webhook_url }" },
          "400": { "description": "Not an https:// URL" }
        }
      }
    },
    "/api/me/export-templates": {
      "get": {
        "summary": "List saved export column templates",
        "description": "Template ids are usable as ?template= on /api/bol/export.",
        "responses": { "200": { "description": "{ templates: [{ id, name, columns }] }" } }
      },
      "put": {
        "summary": "Replace saved export column templates",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "templates": { "type": "array" } } } } }
        },
        "responses": { "200": { "description": "{ ok: true, templates }" } }
      }
    },
    "/api/billing/buy-credits": {
      "post": {
        "summary": "Create a credit-pack checkout link",
        "description": "Returns a Stripe Checkout URL for a one-time credit pack. No charge happens on this call — an agent can fetch the link and hand it to a human, who completes payment on Stripe's hosted page.",
        "responses": { "200": { "description": "{ url } — Stripe Checkout URL" } }
      }
    },
    "/api/mcp": {
      "post": {
        "summary": "MCP server endpoint",
        "description": "Model Context Protocol (streamable HTTP, JSON-RPC 2.0). Tools: extract_bol, extract_batch, list_documents, get_document, save_corrections, get_related_documents, export_documents, get_usage. Same Bearer authentication as the REST API. Rate limit: 60 calls / 5 min per account.",
        "responses": { "200": { "description": "JSON-RPC response" } }
      }
    }
  }
}
