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"
}
}
| Field | Required | Description |
|---|---|---|
filename | Yes | Original file name including its extension. The extension determines how the document is stored, so keep it accurate. |
content_base64 | Yes | The 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_type | No | Content 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
| Condition | Response |
|---|---|
| Decoded document larger than 10 MB | 400 invalid_request |
content_base64 is not valid base64 | 400 invalid_request |
content_base64 decodes to zero bytes | 400 invalid_request |
filename missing or blank | 400 invalid_request |
| Document storage unavailable | 503 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.
| Parameter | Description |
|---|---|
status | pending or completed. |
since / until | Inclusive upload-date window, ISO 8601 (YYYY-MM-DD). |
search | Case-insensitive match across the original file name, the display name and the vendor extracted from the document. |
sort | upload_date (default), original_filename or file_size. |
order | asc 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_idis set), - documents a general-ledger transaction (
transaction_idis set), or - carries a
retention_untildate.
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.
| Event | Fired when |
|---|---|
voucher.created | A document is uploaded. The payload carries id and filename. |
voucher.deleted | A voucher is deleted. The payload carries id. |