API referenceLetters

Create a physical-mail letter

Create (and queue) a physical-mail letter

Endpoint
POST https://api.dairo.app/v1/letters

Queues a PDF to be printed, enveloped, and posted to to. Provide exactly one source: pdfBase64 (a real PDF, max 25 MB decoded) or file, which reuses a PDF already stored in Dairo by attachmentId or objectId. Print options default to mode: "grayscale", sides: "simplex", addressPlacement: "left", and delivery: "economy"; an unsupported to.country is a 422. With autoSend: true (the default) the letter returns as queued and proceeds asynchronously, emitting a letter.status_changed webhook event on every transition; autoSend: false stores a draft that is never mailed. Send an Idempotency-Key header so a retried request returns the already-created letter instead of mailing a second copy. Each mailed letter is billed at its own priced amount — get an exact quote with POST /v1/letters/price first.

Scope: letters:send. Send the API key as a bearer token; Scopes lists what each one reaches.

Request

cURL
curl -X POST "https://api.dairo.app/v1/letters" \
  -H "Authorization: Bearer $DAIRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "letter.pdf",
    "to": {
      "country": "DE",
      "street": "Hauptstraße"
    }
  }'

Headers

NameTypeRequiredDescription
Idempotency-KeystringnoOptional idempotency key for safe retries of side-effecting operations. (max length 128)

Body

Required, application/json: CreateLetterRequest.

Exactly one document source must be provided: pdfBase64, file, or templateId. Validate any letter first with POST /v1/letters/verify; GET /v1/letters/requirements has the full layout spec and compliant starter templates.

FieldTypeRequiredDescription
pdfBase64string or nullnoBase64-encoded PDF (alternative to file).
fileLetterFileRef or nullno
fileNamestringyes
toPostalAddressyes
fromPostalAddress or nullno
printLetterPrintOptionsno
deliveryLetterDeliveryno
autoSendbooleanno(default true)
metadataobjectno
templateIdstring or nullnoA stored letter template id. Dairo renders the template (filled with templateData) and composites the to recipient into the address window — the third document source, mutually exclusive with pdfBase64/file. Required for the structured payment object.
templateDataobject or nullnoValues for the template's {{placeholder}} variables. Ignored unless templateId is set.
dryRunbooleannoWhen true the letter is validated, rendered, and priced exactly like a real send but is NEVER created, mailed, or charged — the response is a non-persisted status:"preview" object. The per-request no-send rehearsal. (default false)
notificationsbooleannoEmail the account owner a status update at notable delivery milestones. (default true)
paymentSlip"qr", "sepaDe", "sepaAt", nullnoBring-your-own-slip flag for a pdfBase64/file letter whose PDF already contains the slip artwork: qr (Swiss QR-bill), sepaDe, or sepaAt. Omit/null for a normal letter. To have Dairo GENERATE the slip use the structured payment object (template letters only).
paymentLetterPayment or nullnoStructured payment object — Dairo GENERATES the payment slip (Swiss QR-bill / SEPA Zahlschein + GiroCode) and composites it full-width at the page bottom. Honored ONLY with templateId; pairing it with pdfBase64/file is a 400.

Response

200 Success. Body: application/json, Letter.

FieldTypeRequiredDescription
object"letter"yes
idstringyes
statusLetterStatusyes
fileNamestring or nullno
pageCountinteger or nullno
toPostalAddressno
fromPostalAddress or nullno
printLetterPrintOptionsno
deliveryLetterDeliveryno
paperTypesarray of LetterPaperTypeno
autoSendbooleanno
priceLetterMoney or nullno
metadataobjectno
lastEventTypestring or nullnoThe type of the newest event on the letter's timeline (e.g. processing, canceled); null until the letter has an event.
lastEventAtstring (date-time) or nullnoWhen the newest timeline event occurred; null until the letter has an event.
submittedAtstring (date-time) or nullno
canceledAtstring (date-time) or nullno
errorstring or nullno
createdAtstring (date-time) or nullno
updatedAtstring (date-time) or nullno
eventsarray of LetterEventno
paymentSlip"qr", "sepaDe", "sepaAt", nullnoThe chosen payment-slip overlay, or null for a normal letter.
dryRunbooleannoTrue for a validate/price-only letter that is never mailed or charged.
providerMode"test", "live"notest: the letter is validated and priced but never printed, mailed, or charged (a dry run, or the account's provider mode is test). live: it will be printed and posted.
notificationsbooleannoOwner milestone-email preference for this letter.
paymentLetterPayment or nullnoThe structured payment object the slip was generated from (owner records), or null.
batchIdstring or nullnoThe owning batch id for a batch-member letter; null for a single send. Pairs with the ?batchId= list filter.
200
{
  "object": "letter",
  "id": "string",
  "status": "draft",
  "fileName": "letter.pdf",
  "pageCount": null,
  "to": {
    "name": "Ada Lovelace",
    "company": "Acme GmbH",
    "street": "Hauptstraße",
    "houseNumber": "1",
    "poBox": null,
    "addressLine2": null,
    "postalCode": "10115",
    "city": "Berlin",
    "country": "DE"
  },
  "from": {
    "name": "Ada Lovelace",
    "company": "Acme GmbH",
    "street": "Hauptstraße",
    "houseNumber": "1",
    "poBox": null,
    "addressLine2": null,
    "postalCode": "10115",
    "city": "Berlin",
    "country": "DE"
  },
  "print": {
    "mode": "color",
    "sides": "simplex",
    "addressPlacement": "left"
  },
  "delivery": "economy",
  "paperTypes": [
    "standard"
  ],
  "autoSend": true,
  "price": {
    "currency": "EUR",
    "amount": 0,
    "walletAmount": 0,
    "walletCurrency": "string",
    "walletRate": 0
  },
  "metadata": {},
  "lastEventType": null,
  "lastEventAt": "2026-10-19T09:30:00Z",
  "submittedAt": "2026-10-19T09:30:00Z",
  "canceledAt": "2026-10-19T09:30:00Z",
  "error": null,
  "createdAt": "2026-10-19T09:30:00Z",
  "updatedAt": "2026-10-19T09:30:00Z",
  "events": [
    {
      "object": "letter_event",
      "eventId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "letterId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "type": "string",
      "code": null,
      "description": null,
      "producer": null,
      "location": null,
      "reason": null,
      "hasImage": true,
      "occurredAt": "2026-10-19T09:30:00Z",
      "recordedAt": "2026-10-19T09:30:00Z"
    }
  ],
  "paymentSlip": "qr",
  "dryRun": true,
  "providerMode": "test",
  "notifications": true,
  "payment": {
    "type": "qr",
    "creditor": {
      "name": "Ada Lovelace",
      "iban": "string",
      "bic": null,
      "street": "Hauptstraße",
      "houseNumber": "1",
      "postalCode": "10115",
      "city": "Berlin",
      "country": "DE"
    },
    "amount": 0,
    "currency": "CHF",
    "reference": null,
    "message": null,
    "debtor": {
      "name": "Ada Lovelace",
      "street": "Hauptstraße",
      "houseNumber": "1",
      "postalCode": "10115",
      "city": "Berlin",
      "country": "DE"
    }
  },
  "batchId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

Errors

Every error has the same body, {"error": {"type", "code", "message", "param"}}; branch on code. Errors lists every code.

StatusMeaning
400The request is malformed or a value is invalid (invalid_request, invalid_domain, idempotency_key_mismatch).
401The API key is missing or invalid.
403The key lacks the scope this endpoint needs (scope_missing) or may not do this (not_permitted).
404No such resource in this account (resource_not_found).
422The request is well-formed but a field failed validation (validation_failed).
429Rate limit, plan limit, budget or spend cap reached (rate_limited, plan_limit_reached, budget_exceeded, spend_cap_reached).
500An unexpected error; retry with backoff, then contact support (internal_error).

On this page