Lähetä kirjeitä omasta järjestelmästäsi
Yksi HTTP-pyyntö, jossa on teksti ja osoite, ja tulostettu, postimaksun kattava kirje kolahtaa postiluukusta. Ei tulostinta, ei postimerkkejä, ei postilaatikkoreissuja.
Luo avainKoko integraatio
Tässä se on kokonaisuudessaan. Vastauksessa tulee tunniste, jolla luet tilan, ja sama tunniste näkyy laskurivilläsi.
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()Avaimet
Jokainen pyyntö kuljettaa API-avaimen bearer-tokenina. Luot avaimet tililläsi ja näet jokaisen täsmälleen kerran; me tallennamme vain tiivisteen. Avaimen mitätöinti astuu voimaan heti.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxKokeile ennen kuin postitat
Avain, joka alkaa sk_test_, hinnoittelee ja muodostaa kirjeen täsmälleen kuten tuotantoavain ja pysähtyy vasta juuri ennen tulostinta. Näin käyt koko integraation läpi ilman että mitään lähtee postiin.
Päätepisteet
| 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) |
Kaikkia tuotteita ei myydä joka kohteeseen. Kysy /api/quote ja lue availableProducts: Alankomaihin ei esimerkiksi ole kirjattua kirjettä lainkaan. Näin voit piilottaa valinnan sen sijaan, että se kaatuisi vasta lähetettäessä.
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.
Tilailmoitukset omiin järjestelmiisi
Kirjeen tila vaihtuu muutaman kerran usean päivän aikana. Kyselyihin perustuva seuranta sopii siihen huonosti: joko kysyt aivan liian usein tai kuulet aivan liian myöhään. Rekisteröi osoite tilillesi, niin me kerromme.
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, ... } }Jokainen ilmoitus on allekirjoitettu. Laske HMAC-SHA256 käsittelemättömästä rungosta omalla salaisuudellasi ja vertaa sitä X-SendLetter-Signature-otsakkeeseen. Tee se ennen rungon jäsentämistä: jäsentäminen ja uudelleen sarjallistaminen muuttavat tavuja, eikä allekirjoitus täsmää enää koskaan.
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()
}Virheet
Jokaisella virheellä on sama muoto: koodi, jonka mukaan haaraudut, ja viesti, jonka luet. Haaraudu koodin mukaan, sillä viesti voi muuttua.
{ "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
Koko määrittely tarjoillaan livenä, joten se ei voi kuvata versiota, joka ei ole ajossa. Osoita generaattori siihen, niin saat asiakaskirjaston omalla kielelläsi.