Letter API documentation (REST, v1)
Our RESTful JSON API lets you send letters straight from your application. Use the test environment with free sandbox keys before going to production. The full OpenAPI/Swagger documentation is at the end of this page.
Getting started
Three steps to your first API letter
Create an account
Sign up for free on briefsenden.com and confirm your email address. With a test key, letters are then neither printed nor charged.
Create an API key
In the dashboard go to Settings → Developer and create an API key. For development use a test key (bv_test_*), for production a live key (bv_live_*).
Send your first letter
Send a PDF with the recipient address via POST to /api/v1/letters/simple. With a test key no physical letter is sent and there is no cost.
Authentication
API keys and the X-Api-Key header
The briefsenden API authenticates every request with an API key. Send it in the X-Api-Key header to the base URL https://www.briefsenden.com/api/v1. All requests must use HTTPS – HTTP requests are rejected.
X-Api-Key: bv_live_YourApiKeyHere
Test keys bv_test_*
For development and testing. Letters are simulated but not physically sent. No cost, no coin deduction – ideal for your development environment.
Live keys bv_live_*
For production. Letters are printed, enveloped and sent via Deutsche Post. The cost is deducted from your coin balance.
You can create, view and revoke API keys at any time in the dashboard under Settings → Developer. Keep your live keys safe and do not share them publicly.
Code examples
Send a letter in your preferred language
curl -X POST https://www.briefsenden.com/api/v1/letters/simple \ -H "X-Api-Key: bv_test_xxx" \ -F "file=@invoice.pdf" \ -F "recipientLine1=Max Mustermann" \ -F "recipientLine2=Musterstraße 1" \ -F "recipientZip=12345" \ -F "recipientCity=Berlin" \ -F "color=bw" \ -F "duplex=simplex"
All examples send a letter via POST /api/v1/letters/simple as multipart/form-data. Replace bv_test_xxx with your API key – a test key costs nothing.
Endpoint overview
The most important API endpoints at a glance
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/letters/simple | Send a letter (PDF file or PDF URL) |
| POST | /api/v1/letters | Send a letter (JSON, PDF as Base64) |
| POST | /api/v1/letters/bulk | Up to 50 letters in one request |
| GET | /api/v1/letters | List letters |
| GET | /api/v1/letters/:id | Get a letter’s status (with tracking number for registered mail) |
| POST | /api/v1/estimate | Calculate the cost of a letter |
| POST | /api/v1/serial/estimate | Calculate the cost of a mail merge |
| GET | /api/v1/balance | Query the balance |
| GET | /api/v1/serial | List mail merges |
| GET | /api/v1/serial/:id | Get a mail merge’s status |
| GET | /api/v1/serial/:id/letters | List the letters of a mail merge |
| DELETE | /api/v1/serial/:id | Cancel a mail merge while it is being processed |
| GET | /api/v1/sender-profiles | List sender profiles (also POST, PUT, DELETE, set-default) |
| GET | /api/v1/addresses | List the address book (also POST, PUT, DELETE) |
| POST | /api/v1/webhooks | Register a webhook |
| GET | /api/v1/webhooks | List webhooks |
| DELETE | /api/v1/webhooks/:id | Delete a webhook |
The full documentation of all endpoints with request and response schemas is in the interactive Swagger UI below.
Pricing, use cases and a quick start for the API: letter and post API for Germany at a glance.
Webhooks
Real-time notifications on status changes
Webhooks notify your application automatically when a letter’s status changes. Set your webhook URL in the dashboard under Settings → Developer. We send a POST request to your URL on every status change.
Event types
letter.createdletter.uploadedletter.processingletter.in_printletter.sentletter.errorserial.completedserial.error
Retry logic
If your server does not respond with a 2xx status code, we retry delivery up to 3 times after 1, 5 and 25 seconds. After 10 consecutive failed deliveries the webhook is deactivated; create it again then.
Payload example
{
"event_type": "letter.sent",
"timestamp": "2026-10-16T14:30:00.1234567Z",
"data": {
"LetterId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"Status": "SENT",
"OldStatus": "IN_PRINT",
"TrackingNumber": null
}
}TrackingNumber contains the tracking number for registered mail once it has been assigned (from handover to the print centre), otherwise null.
Verify the signature
Every delivery carries three headers. Compute the signature over the raw body and compare it in constant time with X-Webhook-Signature. You receive the secret once when creating the webhook.
X-Webhook-Signature- sha256= followed by the HMAC-SHA256 (hex, lower case) over the raw request body; the key is your webhook secret as a UTF-8 string
X-Webhook-Event- Event type, e.g. letter.sent (same as event_type in the body)
X-Webhook-Timestamp- Time of the delivery attempt (ISO 8601, UTC); not part of the signature
const crypto = require('crypto')
// rawBody: the unmodified request body (Buffer), not re-serialised
function isValidSignature(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signatureHeader || '')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}Rate limits & error handling
Limits, status codes and error format
Rate limits
Each API key has its own rate limit: one request can be processed immediately, then another request is released every 500 ms. Up to 10 further requests wait in a queue and are processed in order of arrival. If the queue is full, further requests receive a 429 status code. Wait briefly before retrying.
Idempotency keys
For POST requests you can send an Idempotency-Key header. Successful responses are stored for 24 hours per API key and idempotency key. Repeat the same job with the same key only after the first request has finished; parallel requests are not blocked by it.
Idempotency-Key: unique-request-id-123
Error format (RFC 7807 ProblemDetails)
All errors are returned in the standard RFC 7807 format (ProblemDetails). Validation errors name the affected field under errors; other errors describe the problem in detail. The traceId helps our support find the request. The messages themselves are currently in German, as in this real response:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"pdfUrl": ["pdfUrl muss eine gültige HTTP(S)-URL sein."]
},
"traceId": "de4f753c1a2f48115a926dd3081be05e"
}HTTP status codes
| Code | Meaning |
|---|---|
| 200 | OK – request successful |
| 201 | Created – resource created (e.g. a letter) |
| 400 | Bad Request – invalid parameters |
| 401 | Unauthorized – missing or invalid API key |
| 403 | Forbidden – no permission |
| 404 | Not Found – resource not found |
| 429 | Too Many Requests – rate limit exceeded |
| 500 | Internal Server Error – server error |
API questions
Answers to the most important developer questions
Interactive API documentation
Test endpoints directly in the Swagger UI. The OpenAPI descriptions are in German.
Integrate now
Start with a few lines of code and automate your letter sending via the REST API.