Skicka brev ur ditt eget system
En HTTP-förfrågan med text och en adress, och ett utskrivet, frankerat brev landar i en brevlåda. Ingen skrivare, inga frimärken, ingen vända till brevlådan.
Skapa en nyckelHela integrationen
Det är allt. Svaret innehåller ett id som du läser statusen med, och samma id står på din fakturarad.
curl -X POST https://sendletter.eu/api/v1/letters \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"text": "Geachte heer, mevrouw,\n\nHierbij zeg ik mijn abonnement op.",
"subject": "Opzegging",
"sender": { "name": "Jan de Vries", "street": "Merelstraat", "number": "64",
"postalCode": "8916 AX", "city": "Leeuwarden", "country": "NL" },
"recipient": { "company": "Voorbeeld BV", "name": "Afdeling Klantenservice",
"street": "Hoofdstraat", "number": "1",
"postalCode": "1011 AA", "city": "Amsterdam", "country": "NL" },
"product": "standard",
"idempotencyKey": "opzegging-2026-07-28"
}'const res = await fetch("https://sendletter.eu/api/v1/letters", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SENDLETTER_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text, sender, recipient, product: "registered",
// Optional. Bills a company you belong to instead of
// this key's own wallet; leave it out for a personal
// letter. One you may not spend is refused, not billed
// to you.
organizationId: company.id,
idempotencyKey: invoice.id }),
})
const letter = await res.json()
// letter.id is what you store; poll it or wait for the webhook.
// letter.billedToOrganizationId says which wallet actually paid.$ch = curl_init("https://sendletter.eu/api/v1/letters");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("SENDLETTER_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"text" => $body, "sender" => $sender, "recipient" => $recipient,
"idempotencyKey" => $invoiceId,
]),
]);
$letter = json_decode(curl_exec($ch), true);import os, requests
letter = requests.post(
"https://sendletter.eu/api/v1/letters",
headers={"Authorization": f"Bearer {os.environ['SENDLETTER_KEY']}"},
json={"text": body, "sender": sender, "recipient": recipient,
"idempotencyKey": invoice_id},
timeout=30,
).json()Nycklar
Varje förfrågan bär en API-nyckel som bearer-token. Nycklarna skapar du på ditt konto och ser exakt en gång; vi sparar bara en hash. En spärrad nyckel slutar gälla omedelbart.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxProva innan något postas
En nyckel som börjar med sk_test_ prissätter och renderar brevet precis som en skarp nyckel gör, men stannar strax före skrivaren. Så kan du köra igenom hela integrationen utan att något går ut.
Endpoints
| POST | /api/v1/letters | Send a letter |
| GET | /api/v1/letters | List letters, newest first |
| GET | /api/v1/letters/{id} | One letter, with its timeline |
| POST | /api/quote | Price, and what that country can be sent(no key) |
| GET | /api/countries | Destinations and their address rules(no key) |
| GET | /api/v1/openapi.json | The machine readable spec(no key) |
Alla mottagarländer säljer inte alla produkter. Fråga /api/quote och läs availableProducts: till Nederländerna finns till exempel ingen rekommenderad tjänst. Då kan du släcka alternativet i stället för att låta det fallera vid utskicket.
41 destinations. Registered mail: DE, BE, FR, ES, CH.
Statuses
- queued
- Accepted and waiting on the print partner.
- processing
- An exclusive hand-off to the print partner is in progress.
- attention_required
- The provider result is uncertain; held for reconciliation and never retried automatically.
- submitted
- Handed over to the printer.
- printed
- Printed and enveloped.
- posted
- Handed to the carrier. A response deadline runs from here.
- delivered
- Confirmed delivered. Registered mail only.
- failed
- Not sent. `statusDetail` says why, and the payment is returned.
Statusbesked till dina system
Ett brev byter tillstånd en handfull gånger under några dygn. Att fråga i en slinga passar illa för den takten: antingen frågar du alldeles för ofta eller får veta alldeles för sent. Registrera en endpoint på ditt konto, så hör vi av oss.
POST your-endpoint
X-SendLetter-Event: letter.posted
X-SendLetter-Signature: 9f86d081...
{ "type": "letter.posted",
"sentAt": "2026-07-28T18:30:00.000Z",
"letter": { "id": "AbC123", "status": "posted", "trackingCode": null, ... } }Varje besked är signerat. Räkna ut HMAC-SHA256 över den råa kroppen med din hemlighet och jämför med huvudet X-SendLetter-Signature. Gör det innan du tolkar kroppen: att tolka och serialisera om ändrar byten, och då stämmer signaturen aldrig mer.
import crypto from "node:crypto"
// The raw body, before any JSON parsing.
const expected = crypto.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(rawBody, "utf8").digest("hex")
const sent = req.headers["x-sendletter-signature"]
if (expected.length !== sent.length ||
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sent))) {
return res.status(401).end()
}Fel
Varje fel har samma form: en kod att förgrena logiken på och ett meddelande att läsa. Förgrena på koden, för meddelandet får ändras.
{ "error": { "code": "no_route",
"message": "Registered mail has no route to NL",
"details": null },
"requestId": "req_018f38d7c9f34738adbe1f06a9b36f11" }Every response also carries X-Request-Id. Include that value when asking support about one call; use the letter id to reconcile the complete order.
| 401 | unauthorised | No key, a revoked key, or a malformed header. |
| 400 | invalid_request | The body failed validation. `details` names the fields. |
| 402 | insufficient_balance | The wallet is short. `details` carries required and available. |
| 403 | insufficient_scope / credential_spending_limit | The key lacks a scope or its per-letter ceiling is too low. Nothing is charged. |
| 429 | rate_limited / credential_spending_limit | A request, daily-spend or monthly-spend ceiling was reached. |
| 403 | organization_forbidden | The named organisation exists and this account is in it, but its role may not spend there. Nothing is charged, to it or to you. |
| 404 | organization_not_found | No organisation of that id that this account belongs to. Refused rather than billed to your own wallet. |
| 409 | no_route | That product is not sold to that country. Ask /api/quote first. |
| 413 | too_many_pages | Over the sheet limit for that destination. |
| 404 | not_found | No such letter on this account. |
OpenAPI
Hela specifikationen serveras direkt ur tjänsten, så den kan aldrig beskriva en version som inte kör. Rikta en generator mot den och du har en klient i ditt eget språk.