API-dokumentation
Alle endpoints ligger under https://postly.dk/api/public/v1 og svarer med JSON.
Autentificering
Send din nøgle som bearer token. Nøglen vises kun én gang ved oprettelsen.
Authorization: Bearer pk_postly_xxxxxxxxxxxxxxxxKonto og saldo
GET /api/public/v1/account
{
"id": "...",
"name": "Eksempel ApS",
"balanceOre": 250000,
"status": "active"
}Priser
GET /api/public/v1/pricingSend et brev
Enten text eller file (base64 af en PDF). Beløbet trækkes fra saldoen. Send gerne en Idempotency-Key, så gentagne kald ikke sender to breve. I får en kvittering på mail med det samme og en mail, når brevet er sendt.
POST /api/public/v1/letters
Idempotency-Key: ordre-1234
{
"letterType": "almindelig",
"sender": {
"name": "Eksempel ApS",
"address": "Nordre Fasanvej 207A",
"zip": "2000",
"city": "Frederiksberg"
},
"recipient": {
"name": "Jens Jensen",
"address": "Vestergade 1",
"zip": "8000",
"city": "Aarhus"
},
"text": "Kære Jens, her følger brevet.",
"reference": "faktura-1234"
}Afsendelsesbekræftelse
Når vi har sendt brevet, får I en mail med ordrenummer og afsendelsesdato.
Testnøgler
Opret en testnøgle i /konto/api. Den starter med pk_test_postly_. Et brev sendt med en testnøgle bliver aldrig printet eller sendt, og saldoen bliver ikke trukket. Efter ca. 2 minutter bliver det afleveret til dao med en indleveringskode, der starter med TEST, og et rekommanderet testbrev får sit udfald 3 minutter senere. Med feltet testOutcome vælger I udfaldet: delivered (standard), attempted eller not_collected. Ved delivered kommer en eksempel-PDF som kvittering. Breve fra testnøgler har "livemode": false.
curl https://postly.dk/api/public/v1/letters \
-H "Authorization: Bearer pk_test_postly_xxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: paakrav-lejemaal-12-2026-11" \
-d '{
"letterType": "rekommanderet",
"reference": "Lejemål 12",
"testOutcome": "delivered",
"sender": { "name": "Ejendomsadministrationen", "address": "Vej 1", "zip": "8000", "city": "Aarhus C" },
"recipient": { "name": "Lejer Hansen", "address": "Vej 2, 1. th", "zip": "8000", "city": "Aarhus C" },
"text": "Påkrav ..."
}'Status, reference og kvittering
Hvert brev har jeres reference, en samlet deliveryStatus (processing, handed_to_carrier, og for rekommanderede breve delivered, attempted eller not_collected), dao's indleveringskode i trackingNumber, og om kvitteringen er klar. Det gælder både enkeltbreve og breve i en udsendelse.
GET /api/public/v1/letters?reference=Lejemål%2012
GET /api/public/v1/letters/{id}
GET /api/public/v1/letters/{id}/receipt # dao's kvittering, link i 10 minutter
GET /api/public/v1/letters/{id}/declaration # Postlys afsendelseserklæringWebhooks
Opret en webhook i /konto/api eller via API'et. Vi sender en POST, når et brev er afleveret til dao (letter.handed_to_carrier), når dao melder et udfald (letter.delivery_outcome), og når kvitteringen er klar (letter.receipt_available). Svar med 2xx. Ellers prøver vi igen efter 1 min, 5 min, 30 min, 2 t, 12 t og 24 t. Hemmeligheden vises kun, når webhooken oprettes.
POST /api/public/v1/webhooks { "url": "https://jeres-system.dk/postly" }
GET /api/public/v1/webhooks
POST /api/public/v1/webhooks/{id}/test # sender en signeret test.ping
DELETE /api/public/v1/webhooks/{id}
{
"id": "…",
"event": "letter.delivery_outcome",
"createdAt": "2026-11-06T10:15:00.000Z",
"livemode": true,
"data": { "id": "…", "reference": "Lejemål 12", "deliveryStatus": "delivered", … }
}Tjek signaturen i headeren Postly-Signature (t=<unix>,v1=<hex>): beregn HMAC-SHA256 af t + "." + body med hemmeligheden, sammenlign i konstant tid, og afvis beskeder, der er mere end 5 minutter gamle. Brug den rå body, før den parses.
// Node.js
import crypto from "node:crypto";
function verifyPostly(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return (
expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
);
}# Python
import hmac, hashlib, time
def verify_postly(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts["t"])
if abs(time.time() - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Faktura i stedet for saldo
Efter aftale kan kontoen faktureres månedligt bagud i stedet for at bruge saldo. Så får I aldrig 402, og den 1. i måneden kommer én faktura med alle breve fra måneden før (dato, reference, brevtype, sider og pris) og 8 dages betalingsfrist. Fakturaen ligger i /konto/saldo. GET /account viser faktureringsform og aftalte priser.
Hele API'et er beskrevet i openapi.json. Spørgsmål: gustav@postly.dk.
Fyld saldo op
Returnerer et betalingslink. Saldoen opdateres, når betalingen er gennemført.
POST /api/public/v1/topups
{ "amountOre": 100000 }Fejl
Fejl returneres som { "error": { "code": "...", "message": "..." } } med en passende HTTP-status. Ved for lav saldo får du 402 og koden insufficient_balance.
Grænser
120 kald i timen pr. nøgle. Filer kan maks. fylde 25 MB.
MCP-server
Forbind en AI-assistent til Postly med samme nøgle. Værktøjerne er priser, saldo, send brev, brevstatus og oversigt over breve.
URL: https://postly.dk/api/public/v1/mcp
Auth: Authorization: Bearer pk_postly_xxxxxxxx