Layout & verification

The exact PDF layout a printed letter needs — address window, keep-out zones, fonts — plus the verify endpoint that checks a PDF before you send it.

A letter is printed exactly as your PDF renders, folded into a window envelope, and the recipient address is read optically from page 1 of the PDF. That means the address must sit inside the envelope window, and a few zones must stay clear for franking and postal processing. This page is the full layout contract — and the two endpoints that make it painless:

OperationMethod & pathScope
Get requirementsGET /v1/letters/requirementsletters:read
Verify a letterPOST /v1/letters/verifyletters:read

Building with an AI agent? Point it at GET /v1/letters/requirements first: the response carries this whole spec machine-readable, plus ready-made compliant starter templates it can store and send immediately. Over MCP the same lives on the prepare_letters tool (action: "requirements" and action: "verify"). An agent needs zero prior knowledge of postal layout standards.

The three ways to get a compliant PDF

  1. Use a starter template (easiest). GET /v1/letters/requirements returns starterTemplates[] — compliant letter HTML with {{placeholders}}. Store one via POST /v1/letters/templates, then send with templateId + templateData + to. Dairo composites the recipient into the window for you; the layout is guaranteed.
  2. Store your own template. Write your own HTML (brand, layout, fonts) that keeps the zones below clear, store it as a letter template, and let Dairo render + place the address.
  3. Bring your own PDF. Generate the PDF with any toolchain (HTML-to-PDF, LaTeX, ReportLab, a word processor) following the spec below — including placing the recipient address yourself — and pass it as pdfBase64 or file.

Whichever path you take: verify before you send.

Page setup

RuleValue
FormatA4 portrait — 210 × 297 mm, every page
Max file size25 MB (POST /v1/letters), 4 MB for /verify
FontsEmbed all fonts in the PDF
EncryptionNone — no password protection
AddressReal text, not a scanned image (it is read from the PDF)
Template lettersRender as a single A4 page

All coordinates below are measured in mm from the top-left corner of page 1.

The address window

The recipient address must sit entirely inside one window zone:

PlacementxyWidthHeight
left (default)22 mm60 mm85.5 mm25.5 mm
right118 mm60 mm85.5 mm25.5 mm

Format the block left-aligned at 10–12 pt regular (never bold, underlined, or on a dark background — it is machine-read) with 1.2 line-height, no blank lines, at most 6 lines, in postal order: optional small return-address line (7 pt, underlined), name, company, street + house number (one line) or PO box, postal code + city (one line), then the country line described below. Keep a 3 mm left/right and 2 mm top/bottom inset inside the window. Prefer the left window — it is the postal network's standard machine-read field and the delivery-proven default.

The country line

Letters are printed and posted in Germany. A letter is domestic when the recipient and the sender (from, if you give one) are both in Germany:

LetterRecipient blockReturn-address line
Germany to Germanyno country line (DIN 5008)no country
Switzerland to Germanyends with GERMANYends with SWITZERLAND
Germany to Austriaends with AUSTRIAends with GERMANY

Any letter that is not domestic prints the country name in English capitals, never the ISO code: GERMANY, SWITZERLAND, UNITED KINGDOM. Template letters get this automatically. Follow the same rule in your own PDF. GET /v1/letters/requirements returns it under addressWindow.recipientFormat.countryLine.

Registered mail is different

On a delivery: "registered" letter the carrier prints its registered-mail label and barcode into the y = 50–67 mm band of the address field. The layout shifts:

ElementGeometry
Return-address liney 45–50 mm band (x 20–105 mm)
Carrier label + frankingy 50–67 mm — must stay empty
Recipient blockx 22, y 67, 85.5 × 23 mm

Template letters get this automatically. For your own PDF, place the recipient below 67 mm and verify with "delivery": "registered" — the verifier switches to the registered geometry and fails a normal-position address block. Registered mail always uses the left field and is domestic only. Domestic means the recipient (to.country) is in Germany, where letters are printed and posted. The sender's country does not matter. Price, send, batch send and verify reject delivery: "registered" to any other country with a 422 naming delivery, and verify adds a failing registered_domestic check. Use economy or priority for letters abroad.

When you send with templateId, Dairo composites the recipient from the request's to into this window at render time — a template must never contain an address of its own. The window only concerns you when you bring your own PDF.

Keep-out zones

These zones must contain nothing (except the address block itself inside the franking zone):

ZoneGeometryWhy
Franking, leftx 20, y 40, 89.5 × 47.5 mmPostage/franking marks are stamped here
Franking, rightx 116, y 40, 89.5 × 47.5 mmSame, for right-window letters
Page edges5 mm on all sides, every pagePrinter hardware margin
Bottom-left corner15 × 15 mmPostal processing marks
Left-margin routing codex < 10 mm, y 124–134 mmThe carrier prints its sorting code here
Bottom band110 mm, only with a payment slipThe generated slip is composited here

Two more PDF rules that bite in practice: no interactive form fields (AcroForm — flatten them, or the print output can lose content), and letters over 9 sheets ship unfolded in a C4 envelope automatically.

Body content is safest starting at y ≥ 100 mm with 25 mm left / 20 mm right margins. The top band (y < 38 mm) is free for your letterhead, and the area right of the franking zone fits a date/reference block. Start it at x ≥ 125 mm, y 40–90 mm. That is the DIN 5008 info-block position the din-classic starter uses. The franking keep-out itself ends at x = 109.5 mm.

Verify before sending

POST /v1/letters/verify takes the same document sources as the create (pdfBase64, file, or templateId + templateData + to) and returns a structured verdict — nothing is stored, mailed, or charged. Pass to so the address printed in the PDF is matched against the intended recipient.

curl -X POST https://api.dairo.app/v1/letters/verify \
  -H "Authorization: Bearer $DAIRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pdfBase64": "JVBERi0xLjQK...",
    "to": {
      "name": "Jane Doe", "street": "Hauptstrasse", "houseNumber": "12",
      "postalCode": "8001", "city": "Zürich", "country": "CH"
    }
  }'

valid is true only when no check failed. A fail means the letter will misprint or misroute — fix it. A warn means it will probably print but carries a risk (content near the page edge, unusually small address text, image-only pages the optical address reader may struggle with).

The checks: page_size, page_count, address_window, address_fit, address_lines, address_font_size, address_match (with to), franking_zone, edge_clearance, bottom_left_corner, routing_code_zone, images_in_window, fonts_embedded, form_fields, clipped_content, and registered_domestic for registered letters — each with the expected geometry in mm in its message, so a failing PDF can be regenerated without consulting anything else.

address_font_size holds the recipient lines to 10–12 pt and warns below 10 pt. The small return-address line at the top of the window (8.5 pt or less, 7 pt in Dairo's own layout) is reported separately and is not held to that range.

Clipped content

Text cut off by a box with overflow: hidden (or clip), or pushed past the page edge, is simply missing from the printed letter, and the PDF geometry alone cannot show it. A common cause is a body box with a fixed bottom or height whose sign-off no longer fits. For template letters (templateId) Dairo measures this on the rendered page: clipped_content fails when any text extends beyond the box that clips it or beyond the page, and lists each clipping element with a short path, the first 40 characters of the box's text (boxText), its position (boxTopMm, boxLeftMm), the clipped text, and how far it overflows in mm. Boxes that only crop images or shapes (a logo crop) are not reported. For a PDF you supply (pdfBase64 or file) the check is skipped, so make sure nothing in your own layout is cut off.

{
  "id": "clipped_content",
  "status": "fail",
  "message": "1 box(es) cut off text: \"Kind regards, Jane Doe Head of Operations\" is cut off by the box starting \"Lorem ipsum dolor sit amet, consectetur …\" at top 100mm, left 25mm (`div.letter-body`, overflow:hidden/clip), 19.7mm past its bottom edge. …",
  "actual": {
    "clipped": [
      {
        "path": "div.letter-body",
        "reason": "overflow_clip",
        "excerpt": "Kind regards, Jane Doe Head of Operations",
        "boxText": "Lorem ipsum dolor sit amet, consectetur …",
        "overflowMm": { "top": 0, "right": 0, "bottom": 19.7, "left": 0 },
        "clipRectMm": { "xMm": 25, "yMm": 100, "widthMm": 165.1, "heightMm": 179.1 },
        "boxTopMm": 100,
        "boxLeftMm": 25
      }
    ]
  }
}

reason is overflow_clip for an element with overflow: hidden/clip, and beyond_page (with path: "page") for text past the A4 page edge.

Sending a template letter whose text is cut off is rejected before anything is stored or charged:

{
  "error": {
    "type": "invalid_request_error",
    "code": "letter_content_clipped",
    "param": "templateId",
    "message": "1 box(es) in the rendered template cut off text (\"Kind regards, Jane Doe\"); clipped text would be missing from the printed letter. ..."
  }
}

Let the box grow, shorten the text, or reduce the font size, then verify again.

The safe sending workflow

  1. GET /v1/letters/requirements — spec + starter templates (agents: start here).
  2. Build the document (template or your own PDF).
  3. POST /v1/letters/verify — fix every fail, review every warn.
  4. POST /v1/letters/price — preview the cost.
  5. POST /v1/letters with dryRun: true — a full no-send rehearsal: validated, rendered, priced; never mailed, never charged.
  6. POST /v1/letters — the real send, with an Idempotency-Key.

Next steps

On this page