> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dairo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify a letter PDF's layout before sending

> Verify a letter PDF's layout before sending



## OpenAPI

````yaml POST /v1/letters/verify
openapi: 3.1.0
info:
  title: Dairo API
  version: 1.0.0
  summary: Email infrastructure API for AI agents and applications.
  description: >-
    Source-of-truth public Dairo API contract. Operations marked
    x-dairo-status=live are implemented; planned operations define the SDK/CLI
    target surface.
servers:
  - url: https://api.dairo.app
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Messages
    description: Outbound email sending.
  - name: Domains
    description: >-
      A domain lets you send and receive on addresses you own
      (`hello@yourcompany.com`) instead of the shared `dairo.app` domain. Add a
      domain, publish the DNS records Dairo returns, then recheck it to move
      from `pending` to `verified`. Every account also has read-only access to
      the included `dairo.app` domain, which is always verified and cannot be
      modified.
  - name: Inboxes
    description: >-
      An inbox is a send-and-receive address on one of your domains
      (`billing@yourcompany.com`) that carries a routing `mode`, an assigned
      `agent` name, and a `status` of `active`, `paused`, or `deleted`. Create
      an inbox on a `verified` domain, then mail sent to it is stored as a
      message and delivered to your webhooks as a `message.received` event. You
      can also mint an inbox on the shared `dairo.app` domain to receive without
      adding a domain of your own.
  - name: Webhooks
    description: >-
      Register HTTPS endpoints that receive Dairo account events — new inbound
      mail, delivery and bounce receipts, budget trips, letter and domain status
      changes, and more — as signed JSON `POST` requests. Each subscription has
      its own signing secret, and deliveries are retried automatically, logged
      per attempt, and can be paused, resumed, redriven, or test-pinged. Verify
      every request with the `X-Dairo-Signature` header before trusting its
      payload.
  - name: API Keys
    description: >-
      Programmatic credentials for the Dairo API. Each key holds an explicit set
      of `scopes` and an optional IP allowlist, and its secret is returned only
      once — at creation. A key that carries `keys:write` can mint additional
      keys, but only with scopes that are a subset of its own.
  - name: Messages
    description: >-
      Send messages and read what comes back, across every channel Dairo
      carries. One `POST /v1/messages` call delivers email, agent-to-agent,
      Telegram, Slack, or SMS from one of your inboxes, and `GET /v1/messages`
      returns inbound, outbound, and internal messages as a single unified list.
      The same surface exposes a send's delivery events, scheduled-send
      cancellation, bulk deletion, Telegram message actions, and attachment
      metadata.
  - name: Threads
    description: >-
      Threads group the inbound and outbound messages of a single email or
      agent-to-agent conversation under one `subject`, scoped to the inbox they
      belong to. Dairo opens and updates threads automatically as messages
      arrive and send, so this resource is read-only: list threads by recent
      activity, or fetch one thread with its messages. Both calls require the
      `messages:read` scope.
  - name: Audiences
    description: >-
      Reusable email recipient lists and list broadcasts. Create an audience,
      add members by raw address or by contact reference, then broadcast a
      single message that fans out asynchronously to every active member.
  - name: Outbound
    description: Outbound email history and delivery events.
  - name: Templates
    description: >-
      Reusable email templates written as React email components, backed by an
      append-only history of immutable versions. Creating a template publishes
      version `1`; publish a new version whenever the content changes, and
      re-point the mutable `currentVersion` pointer to roll forward or back
      instantly. Reference a template by `id` or `slug` when you [send a
      message](/sending/sending-email), and Dairo validates your `variables`
      against the pinned version's schema before the message goes out.
  - name: Budgets
    description: >-
      Budgets are fail-closed spending and volume ceilings enforced inside the
      send path. Attach one to your whole `account`, to a single API `key`, or
      to an `agent`, and every send is checked against its limits before it is
      accepted. A send over the line is rejected with a `budget_exceeded` error
      and emits a `budget.tripped` event.
  - name: Events
    description: >-
      A durable, ordered ledger of every delivery event on your account —
      outbound sends, delivery outcomes such as `email.delivered` and
      `email.bounced`, and inbound `message.received`. Read it forward from a
      cursor to catch up after downtime, spot lost events by their sequence
      gaps, and replay any slice back to your webhooks. Each ledger row mirrors
      the webhook event for the same source event, so the same `eventId` lines
      up across live delivery and replay.
  - name: Agents
    description: >-
      An Agent Passport gives one of your agents a durable, portable identity: a
      stable `agt_...` id and an account-wide Ed25519 signing key. Bind an API
      key or an inbox to a passport and every message that key or inbox sends
      carries a signed `X-Dairo-Provenance` header, letting any recipient
      confirm the message really came from your agent by checking it against
      Dairo's public JWKS. The verify and JWKS endpoints are public and
      unauthenticated; creating, reading, binding, and deleting passports run on
      your API key.
  - name: Compliance
    description: >-
      Prove and enforce EU data governance from your own automation. Export a
      tamper-evident, hash-chained audit ledger with an `EdDSA`-signed
      verification manifest, run irreversible subject-erasure and inbox-purge
      jobs that return signed deletion certificates, and read a
      machine-checkable residency posture with a monotonic subprocessor list —
      every signed artifact verifies against Dairo's published JWKS, so an
      auditor never has to trust the API on its word.
  - name: Conversation Autopilot
    description: >-
      Register a durable background wait on an inbox and let Dairo resolve an
      inbound email for you. Each wait watches the inbox's incoming mail; an
      EU-resident AI matcher decides which message satisfies a natural-language
      `instruction` and extracts the value you asked for — a one-time code, a
      reset link, or a confirmation line. The outcome arrives as a
      `verification.resolved` or `verification.expired` event on your webhook
      and the durable event ledger, or by polling the wait.
  - name: Letters
    description: >-
      Send physical mail by API. Dairo prints your PDF, envelopes it, and posts
      it, then keeps a live `status` and event timeline through delivery.
      Templates and batches personalize one design and send it to up to 1,000
      recipients per call.
  - name: Buckets
    description: >-
      Buckets are owner-scoped containers for stored files. Every account starts
      with a default bucket, plus a reserved bucket that holds inbound email
      attachments. Uploads follow a two-step flow — request an upload URL, `PUT`
      the bytes to it, then finalize to record the object — and every object is
      malware-scanned before it can be shared. Reads require the `buckets:read`
      scope; writes require `buckets:write`.
  - name: Contacts
    description: >-
      A channel-agnostic address book. Each contact is one identity — a
      `person`, an `agent`, or the project `self` contact — with any number of
      `handles`, one per channel address (`email`, `a2a`, `telegram`, `postal`,
      and more). Store a contact once and address it anywhere by `@alias`,
      `contact:<id>`, or `@me` instead of repeating raw addresses; Dairo also
      links inbound messages to the sender's contact so you can read a full
      cross-channel history.
  - name: Phone
    description: >-
      Place outbound AI phone calls and manage the phone numbers that place
      them. A call is asynchronous — you place it, then poll for its status,
      transcript, recording, and a structured summary — while numbers are
      searched in carrier inventory, provisioned, bound to an inbox or agent,
      and released. Reads use the `phone:read` scope, number provisioning uses
      `phone:write`, and dialing a person uses `phone:call`.
  - name: Slack
    description: >-
      Connect Slack workspaces so a bot running on your own Slack app appears in
      the unified inbox. Register a managed or bring-your-own Slack app, mint an
      "Add to Slack" install URL for your customers, and inbound @mentions and
      DMs arrive as threads that fire the `message.received` webhook. See
      [connecting Slack](/channels/slack) for the end-to-end flow.
  - name: Account
    description: >-
      Introspect the account behind the API key making the request — its
      identity, the key's scopes, the current plan, live usage counters, and
      remaining storage. The resource is a single read-only call you use to
      confirm a key works and to check headroom before a large send or upload.
  - name: Attachments
    description: >-
      Attachments are the files carried on a message — the documents and images
      on inbound email, and files you attach when sending. These endpoints read
      a stored attachment: fetch its raw bytes directly, or mint a short-lived
      signed URL you can hand to a browser or another service. Every read is
      scoped through the message that owns the attachment and gated by a
      security scan, and all three require the `messages:read` scope.
  - name: Audit Logs
    description: >-
      An append-only, tamper-evident record of governance-relevant control-plane
      actions on your account — resource creation and deletion, API key and
      webhook management, membership and organization changes. Entries are
      written automatically as you and your keys act; they cannot be created,
      edited, or deleted through the API. Reading requires the `account:read`
      scope.
  - name: MCP
    description: >-
      A single public endpoint that advertises the Dairo hosted MCP server's
      tool surface — which tools an agent can call, the scopes each one needs,
      and a content-hash version you can poll cheaply. Point an MCP-aware client
      at it to discover Dairo's tools without authenticating, or pass a key to
      see which tools that key is allowed to call.
  - name: Notifications
    description: >-
      Control which categories of account email Dairo sends you. Preferences are
      per category — `account`, `billing`, `usage`, `security`, and `product` —
      and each defaults to enabled, so a new account receives every notification
      until you opt a category out.
  - name: Organizations
    description: >-
      An organization is a Dairo project — the isolation tier that owns your
      domains, inboxes, API keys, and every other resource, and that billing and
      membership attach to. Every account belongs to at least one organization
      and holds a `role` of `owner`, `admin`, or `member` in each. These
      dashboard-authenticated endpoints back the organization switcher: they
      list the organizations you belong to and create new ones.
  - name: Reputation
    description: >-
      Every agent carries its own bounce and complaint reputation, tracked over
      a rolling 24-hour window and isolated from your domain and from your other
      agents. When an agent's rates climb, Dairo throttles it and then pauses it
      automatically to protect your sending reputation, exposing the current
      state, the thresholds in force, and a `shouldSend` verdict you can check
      before a batch. Use these endpoints to watch the fleet and to clear an
      agent once you've fixed the underlying cause.
  - name: Schema Inboxes
    description: >-
      Attach an extraction contract to an inbox so inbound mail arrives as
      typed, validated JSON instead of best-effort text. The contract is a flat
      `JSON-Schema-lite` map of field names to type declarations; each incoming
      message is extracted against it, and any extracted object that fails
      validation is either quarantined or passed through per the inbox's policy.
      These endpoints are the control plane — attach, read, and detach the
      contract — while extraction runs automatically on every message the inbox
      receives.
  - name: Telegram
    description: >-
      The catalog of text-to-speech voices you can attach to a
      [Telegram](/channels/telegram) voice message. Browse it to find a voice,
      then pass that voice's `slug` as `telegram.voice.voiceId` when you send.
      Omitting `voiceId` uses the account default voice (`adrian`).
paths:
  /v1/letters/verify:
    post:
      tags:
        - Letters
      summary: Verify a letter PDF's layout before sending
      description: >-
        Layout-verifies a letter BEFORE it is sent and returns a structured
        verdict — no side effects, nothing stored, nothing mailed, nothing
        charged. Checks: every page is A4 portrait; the recipient address sits
        inside the address window (left x=22mm y=60mm 85.5×25.5mm; right x=118mm
        — mm from the top-left corner; REGISTERED letters use the registered
        window y=67–90mm below the carrier's label band); the franking keep-out,
        5mm edge band, 15mm bottom-left corner, and left-margin routing-code
        zone are clear; fonts are embedded; no interactive form fields; and,
        when `to` is provided, the address PRINTED in the PDF matches the
        intended recipient (the carrier mails to what is printed). Accepts the
        same document sources as the create — pass the SAME `delivery` and
        `payment` you will send with, so the `templateId` source verifies the
        exact PDF a create with the same inputs would mail. Every agent should
        call this before a real send and fix every `fail` (and review every
        `warn`). Requires only `letters:read`.
      operationId: verifyLetter
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LetterVerifyRequest'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LetterVerification'
        '400':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    LetterVerifyRequest:
      type: object
      description: >-
        Exactly one document source must be provided: `pdfBase64`, `file`, or
        `templateId` (with `templateData` + `to` — the render composites the
        recipient). Passing `to` is strongly recommended: it enables the
        printed-address match check.
      properties:
        pdfBase64:
          type:
            - string
            - 'null'
          description: Base64-encoded PDF to verify (max 4 MB for verification).
        file:
          oneOf:
            - $ref: '#/components/schemas/LetterFileRef'
            - type: 'null'
        templateId:
          type:
            - string
            - 'null'
          description: Verify a stored template's render. Requires `to`.
        templateData:
          type:
            - object
            - 'null'
          additionalProperties: true
        to:
          oneOf:
            - $ref: '#/components/schemas/PostalAddress'
            - type: 'null'
          description: >-
            The intended recipient — enables the printed-address match check.
            Required with `templateId`.
        from:
          oneOf:
            - $ref: '#/components/schemas/PostalAddress'
            - type: 'null'
          description: Optional return address (template path only).
        print:
          $ref: '#/components/schemas/LetterPrintOptions'
        maxPages:
          type:
            - integer
            - 'null'
          minimum: 1
          description: Optional page-count ceiling enforced as a fail check.
        delivery:
          oneOf:
            - $ref: '#/components/schemas/LetterDelivery'
            - type: 'null'
          description: >-
            Pass the SAME delivery class you will send with. `registered`
            letters use a DIFFERENT address geometry — the carrier prints its
            registered-mail label into y=50–67mm, so the recipient block must
            sit in y=67–90mm — and are verified against it.
        payment:
          oneOf:
            - $ref: '#/components/schemas/LetterPayment'
            - type: 'null'
          description: >-
            The structured payment object the create will carry. Template
            letters are rendered WITH the generated slip so the verified PDF is
            the exact PDF the create would mail. Template-path only (mirrors the
            create's rule).
    LetterVerification:
      type: object
      required:
        - object
        - valid
        - checks
      description: The structured layout verdict for a letter PDF.
      properties:
        object:
          type: string
          enum:
            - letter_verification
        valid:
          type: boolean
          description: True when NO check failed. Warnings may still be present.
        pageCount:
          type: integer
        pageSize:
          type:
            - object
            - 'null'
          properties:
            widthMm:
              type: number
            heightMm:
              type: number
        addressPlacement:
          type:
            - string
            - 'null'
          enum:
            - left
            - right
            - null
          description: >-
            The window where the recipient address was detected, or null when
            none was found.
        addressLines:
          type: array
          items:
            type: string
          description: The text lines detected inside the address window, top to bottom.
        checks:
          type: array
          items:
            $ref: '#/components/schemas/LetterVerificationCheck'
        summary:
          type: string
        agentHint:
          type: string
          description: The recommended next step.
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/Error'
      additionalProperties: false
    LetterFileRef:
      type: object
      description: >-
        Reuse a PDF already stored in Dairo as the letter — no re-upload.
        Provide EXACTLY ONE of `attachmentId` (an email attachment) or
        `objectId` (a storage object from the buckets API). Providing both, or
        neither, is a 400.
      properties:
        attachmentId:
          type: string
          description: An email attachment id to reuse as the letter PDF.
        messageId:
          type:
            - string
            - 'null'
          description: Optional message id the attachment belongs to (informational).
        objectId:
          type: string
          description: >-
            A storage object id (from POST /v1/buckets/{bucketId}/objects upload
            + finalize) to reuse as the letter PDF. Must have passed its malware
            scan (clean/skipped).
    PostalAddress:
      type: object
      required:
        - country
      properties:
        name:
          type:
            - string
            - 'null'
        company:
          type:
            - string
            - 'null'
        street:
          type:
            - string
            - 'null'
        houseNumber:
          type:
            - string
            - 'null'
        poBox:
          type:
            - string
            - 'null'
        addressLine2:
          type:
            - string
            - 'null'
        postalCode:
          type:
            - string
            - 'null'
        city:
          type:
            - string
            - 'null'
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code.
    LetterPrintOptions:
      type: object
      properties:
        mode:
          $ref: '#/components/schemas/LetterPrintMode'
        sides:
          $ref: '#/components/schemas/LetterSides'
        addressPlacement:
          $ref: '#/components/schemas/LetterAddressPlacement'
    LetterDelivery:
      type: string
      enum:
        - economy
        - priority
        - registered
        - bulk
        - premium
    LetterPayment:
      type: object
      required:
        - type
        - creditor
        - amount
      description: >-
        A generated payment slip: `qr` = Swiss QR-bill (CHF), `sepaDe`/`sepaAt`
        = SEPA Zahlschein + GiroCode (EUR). The debtor defaults to the letter
        recipient.
      properties:
        type:
          type: string
          enum:
            - qr
            - sepaDe
            - sepaAt
        creditor:
          type: object
          required:
            - name
            - iban
            - country
          description: The payee (account holder).
          properties:
            name:
              type: string
            iban:
              type: string
            bic:
              type:
                - string
                - 'null'
            street:
              type:
                - string
                - 'null'
            houseNumber:
              type:
                - string
                - 'null'
            postalCode:
              type:
                - string
                - 'null'
            city:
              type:
                - string
                - 'null'
            country:
              type: string
              description: ISO 3166-1 alpha-2.
        amount:
          type: number
          exclusiveMinimum: 0
          description: Amount > 0, at most 2 decimals.
        currency:
          type:
            - string
            - 'null'
          enum:
            - CHF
            - EUR
            - null
          description: Must match `type` (qr => CHF, sepa* => EUR); omitted = inferred.
        reference:
          type:
            - string
            - 'null'
          description: Structured payment reference.
        message:
          type:
            - string
            - 'null'
          description: Unstructured remittance info (Verwendungszweck).
        debtor:
          oneOf:
            - $ref: '#/components/schemas/LetterPaymentParty'
            - type: 'null'
    LetterVerificationCheck:
      type: object
      required:
        - id
        - status
        - message
      description: >-
        One layout check verdict. `fail` blocks a compliant send; `warn` is a
        risk to review; `skipped` means the check was not applicable.
      properties:
        id:
          type: string
          description: >-
            Stable check id, e.g. `page_size`, `address_window`,
            `address_match`, `franking_zone`, `edge_clearance`,
            `fonts_embedded`.
        status:
          type: string
          enum:
            - pass
            - warn
            - fail
            - skipped
        message:
          type: string
          description: Agent-readable finding, with expected geometry in mm where relevant.
        expected:
          description: Machine-usable expected value, when applicable.
        actual:
          description: Machine-usable observed value, when applicable.
    Error:
      type: object
      required:
        - message
      properties:
        message:
          type: string
        code:
          type: string
        type:
          type:
            - string
            - 'null'
        param:
          type:
            - string
            - 'null'
      additionalProperties: false
    LetterPrintMode:
      type: string
      enum:
        - color
        - grayscale
    LetterSides:
      type: string
      enum:
        - simplex
        - duplex
    LetterAddressPlacement:
      type: string
      enum:
        - left
        - right
    LetterPaymentParty:
      type: object
      properties:
        name:
          type: string
        street:
          type:
            - string
            - 'null'
        houseNumber:
          type:
            - string
            - 'null'
        postalCode:
          type:
            - string
            - 'null'
        city:
          type:
            - string
            - 'null'
        country:
          type:
            - string
            - 'null'
          description: ISO 3166-1 alpha-2.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Dairo API key, e.g. dairo_test_... or dairo_live_...

````