API documentation

Invoice walkthrough

Follow one realistic PDF invoice from upload through completed JSON and webhook delivery.

Sample document

This walkthrough uses a realistic cross-border PDF invoice from a German supplier to a Malta business customer. The document uses EU B2B reverse charge, so tax is not seller-collected even though the supply is taxable.

Invoice RE-2026-1048 Meyer Supply GmbH
Recipient
Northwind Operations Ltd.
Issue date
2026-05-10
Due date
2026-06-09
Net amount
EUR 1,079.50
VAT treatment
Reverse charge

Upload request

Upload the PDF with a stable idempotency key. Store the key with the local job so retries remain safe.

Upload
curl -sS -X POST "https://www.exdata.app/api/v1/documents" \
  -H "Authorization: Bearer $EXDATA_API_TOKEN" \
  -H "Idempotency-Key: invoice-re-2026-1048" \
  -F "file=@./invoice-re-2026-1048.pdf" \
  -F "locale=en" \
  -F "custom_types[]=invoice" \
  -F "requester=accounts-payable"

Status response

The first response confirms that exdata accepted the document. Processing is asynchronous, so status can be pending until extraction completes.

201 response
{
  "data": {
    "id": 123,
    "mode": "test",
    "status": "pending",
    "processing_stage": "queued",
    "filename": "invoice-re-2026-1048.pdf",
    "file_format": "pdf",
    "file_size": 240123,
    "custom_types": ["invoice"],
    "requester": "accounts-payable",
    "locale": "en",
    "latest_extraction_run": {
      "id": 456,
      "mode": "test",
      "source": "api",
      "status": "pending",
      "extractor_version": "document:2026-05-17",
      "normalization_version": "base:2026-05-10",
      "created_at": "2026-05-10T01:00:03.000000Z"
    },
    "created_at": "2026-05-10T01:00:00.000000Z",
    "updated_at": "2026-05-10T01:00:03.000000Z"
  }
}

Final JSON

Once the document is completed, map from the normalized value keys and keep candidates for review screens or support diagnostics.

Extraction response
{
  "data": {
    "type": {"value": "invoice", "candidates": ["Invoice"]},
    "document_number": {"value": "RE-2026-1048", "candidates": ["RE-2026-1048"]},
    "issue_date": {"value": "2026-05-10", "candidates": ["10 May 2026"]},
    "payment_due_date": {"value": "2026-06-09", "candidates": ["Due 09/06/2026"]},
    "sender_name": {"value": "Meyer Supply GmbH", "candidates": ["Meyer Supply GmbH"]},
    "sender_country_code": {"value": "DE", "candidates": ["Germany"]},
    "sender_vat_number": {"value": "DE123456789", "candidates": ["VAT ID DE123456789"]},
    "recipient_name": {"value": "Northwind Operations Ltd.", "candidates": ["Northwind Operations Ltd."]},
    "recipient_country_code": {"value": "MT", "candidates": ["Malta"]},
    "recipient_vat_number": {"value": "MT12345678", "candidates": ["VAT MT12345678"]},
    "currency": {"value": "EUR", "candidates": ["EUR"]},
    "net_amount": {"value": "1079.50", "candidates": ["Net amount EUR 1,079.50"]},
    "gross_amount": {"value": "1079.50", "candidates": ["Total due EUR 1,079.50"]},
    "tax_amount": {"value": "0.00", "candidates": ["VAT reverse charge"]},
    "tax_system": {"value": "vat", "candidates": ["VAT"]},
    "tax_collection_mechanism": {"value": "reverse_charge", "candidates": ["Reverse charge"]},
    "cross_border_tax_treatment": {"value": "eu_b2b_service", "candidates": ["Article 196 VAT Directive"]},
    "tax_breakdowns": {
      "value": [
        {
          "taxable_amount": "1079.50",
          "tax_amount": "0.00",
          "tax_rate": "0.00",
          "taxability": "taxable",
          "tax_collection_mechanism": "reverse_charge",
          "tax_exemption_reason": "Intra-EU B2B reverse charge"
        }
      ],
      "candidates": ["Reverse charge applies under Article 196 VAT Directive"]
    },
    "payment_reference": {"value": "RE-2026-1048", "candidates": ["Payment reference RE-2026-1048"]},
    "iban": {"value": "DE89370400440532013000", "candidates": ["IBAN DE89 3704 0044 0532 0130 00"]}
  }
}

Webhook payload

If your endpoint subscribes to document.completed, exdata sends the completed document resource to your receiver with the same document and extraction fields.

document.completed
{
  "id": "1d9d0f2b-4eb8-4c94-a636-7a71f8b6a071",
  "type": "document.completed",
  "created_at": "2026-05-10T01:00:19.000000Z",
  "data": {
    "document": {
      "id": 123,
      "mode": "test",
      "status": "completed",
      "filename": "invoice-re-2026-1048.pdf",
      "extractions": {
        "document_number": {"value": "RE-2026-1048", "candidates": ["RE-2026-1048"]},
        "gross_amount": {"value": "1079.50", "candidates": ["Total due EUR 1,079.50"]},
        "tax_collection_mechanism": {"value": "reverse_charge", "candidates": ["Reverse charge"]}
      }
    }
  }
}

Suggested mapping

Start with deterministic mappings and make review rules explicit. This keeps the first integration understandable while still preserving evidence for finance or support teams.

TargetUseReview rule
Vendor matchsender_name, sender_vat_number, ibanReview first payment to a new IBAN.
Duplicate checkdocument_number, sender_vat_number, gross_amountBlock exact duplicate before posting.
Tax codetax_breakdowns.value[].tax_collection_mechanismRoute reverse_charge to the reverse-charge tax code.
Payment rungross_amount, currency, payment_due_date, payment_referenceRequire due date fallback rules before automation.