Guides

Vouchers

A voucher (Danish bilag) is the document that supports a bookkeeping entry — a supplier invoice, a receipt, a contract. Upload documents through the API, attach them to expenses, and download them back. Vouchers require the vouchers:read and vouchers:write scopes.

Endpoints

  • GET/api/v1/vouchersList vouchers
  • POST/api/v1/vouchersUpload a document
  • GET/api/v1/vouchers/{id}Voucher metadata
  • GET/api/v1/vouchers/{id}/contentDownload the document
  • DELETE/api/v1/vouchers/{id}Delete an unattached voucher

Uploading a document

The whole public API speaks JSON, so the file travels base64-encoded in the request body rather than as multipart form data. Send the original file name, the encoded bytes and the content type:

curl -X POST https://app.talero.dk/api/v1/vouchers \
  -H "X-API-Key: $TALERO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 018f6c1e-7b3d-7e2a-9c41-5d6e7f8a9b0c" \
  -d "$(jq -n \
        --arg filename "bilag-2026-118-dansk-kontorforsyning.pdf" \
        --arg content "$(base64 < bilag-2026-118.pdf | tr -d '\n')" \
        '{ filename: $filename, content_base64: $content, mime_type: "application/pdf" }')"
Response — 201 Created
{
  "data": {
    "id": "3a5c7e9b-1d2f-44a6-b8c0-9e7d5f3b1a2c",
    "filename": "bilag-2026-118-dansk-kontorforsyning.pdf",
    "mime_type": "application/pdf",
    "file_size": 184320,
    "status": "pending"
  }
}
FieldRequiredDescription
filenameYesOriginal file name including its extension. The extension determines how the document is stored, so keep it accurate.
content_base64YesThe file, standard base64 (RFC 4648, padded — not base64url), as a single unbroken string. Leading and trailing whitespace is trimmed, but embedded line breaks are not — strip the line wrapping many base64 tools add by default.
mime_typeNoContent type stored with the document and replayed on download. Defaults to application/octet-stream.

An Idempotency-Key header is required, exactly as on the other create endpoints — a retried upload returns the original response with X-Idempotency-Replayed: true instead of storing the document twice. See Idempotency.

Size limits and failures

ConditionResponse
Decoded document larger than 10 MB400 invalid_request
content_base64 is not valid base64400 invalid_request
content_base64 decodes to zero bytes400 invalid_request
filename missing or blank400 invalid_request
Document storage unavailable503 service_unavailable

Base64 inflates the payload by roughly one third, so a 10 MB document is about 13.4 MB on the wire. The document is written to storage before the voucher row is created: a 503 therefore leaves no voucher behind and is safe to retry with the same idempotency key.

Listing and filtering

The list endpoint follows the standard pagination and filtering conventions.

ParameterDescription
statuspending or completed.
since / untilInclusive upload-date window, ISO 8601 (YYYY-MM-DD).
searchCase-insensitive match across the original file name, the display name and the vendor extracted from the document.
sortupload_date (default), original_filename or file_size.
orderasc or desc (default desc).
curl "https://app.talero.dk/api/v1/vouchers?status=pending&since=2026-02-01&sort=file_size&order=desc" \
  -H "X-API-Key: $TALERO_API_KEY"
Response — 200 OK
{
  "data": [
    {
      "id": "3a5c7e9b-1d2f-44a6-b8c0-9e7d5f3b1a2c",
      "filename": "bilag-2026-118-dansk-kontorforsyning.pdf",
      "mime_type": "application/pdf",
      "file_size": 184320,
      "status": "completed",
      "expense_id": "6d2a8f4b-3c1e-4d5f-9b8a-7c6e5d4f3a2b",
      "transaction_id": "",
      "uploaded_at": "2026-02-10T11:44:02Z",
      "retention_until": "2031-02-10"
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total": 57, "total_pages": 2 }
}

GET /api/v1/vouchers/{id} returns a single voucher in the same shape. Fields that are not set — expense_id, transaction_id, retention_until on a fresh upload — are returned as empty strings rather than null.

Downloading the document

The content endpoint returns the stored bytes, not a JSON envelope. The Content-Type is the voucher's own mime_type, and Content-Disposition offers the file under its original name:

curl -OJ "https://app.talero.dk/api/v1/vouchers/3a5c7e9b-1d2f-44a6-b8c0-9e7d5f3b1a2c/content" \
  -H "X-API-Key: $TALERO_API_KEY"
Response — 200 OK
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="bilag-2026-118-dansk-kontorforsyning.pdf"

%PDF-1.7 ...

Read the content type from the response header rather than assuming PDF — images and other formats are stored and replayed exactly as uploaded.

Attaching a voucher to an expense

A voucher on its own documents nothing. Attach it by sending its id as voucher_id when you create the expense:

curl -X POST https://app.talero.dk/api/v1/expenses \
  -H "X-API-Key: $TALERO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 018f6c1e-8c4e-7f3b-a052-6e7f8a9b0c1d" \
  -d '{
    "supplier_id": "2e8b6c4d-9f1a-4e3b-8c7d-5a2f4b6d8e0c",
    "voucher_id": "3a5c7e9b-1d2f-44a6-b8c0-9e7d5f3b1a2c",
    "date": "2026-02-10",
    "due_date": "2026-03-10",
    "description": "Kontorartikler og printerpapir",
    "amount": 1250.00,
    "vat_amount": 312.50
  }'

The voucher must belong to the same company as the API key, or the request fails with 400 invalid_request. The natural sequence for an integration is therefore: upload the document, read the returned id, then create the expense that references it.

Deletion and retention

Danish bookkeeping law (bogføringsloven) requires accounting records and their supporting documents to be retained for five years from the end of the financial year they concern. Talero enforces this on the API:

curl -X DELETE "https://app.talero.dk/api/v1/vouchers/3a5c7e9b-1d2f-44a6-b8c0-9e7d5f3b1a2c" \
  -H "X-API-Key: $TALERO_API_KEY"
Response — 200 OK
{ "data": { "id": "3a5c7e9b-1d2f-44a6-b8c0-9e7d5f3b1a2c", "deleted": true } }

The delete is rejected with 400 invalid_request when the voucher:

  • documents an expense (expense_id is set),
  • documents a general-ledger transaction (transaction_id is set), or
  • carries a retention_until date.

Only unattached uploads can be removed through the API. To discard a voucher that is already attached, detach it from the expense in the Talero application first — and only where the retention obligation genuinely does not apply.

Webhook events

Two events cover the voucher lifecycle. Subscribe to them like any other event — see Webhooks.

EventFired when
voucher.createdA document is uploaded. The payload carries id and filename.
voucher.deletedA voucher is deleted. The payload carries id.