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

# Download letter PDF

> The printable letter: a single US Letter page with sender and
recipient blocks, the amount panel (sealed letters print
`••••••• cAROUSD`), the QR encoding `claimUrl`, the claim code for
manual entry, the expiry date and a one-time-claim warning.

Available from `ESCROWED` onwards. `DRAFT` and `FAILED` letters return
`409 letter_not_printable`. `CLAIMED`, `VOIDED` and `EXPIRED` letters
render with a large `CLAIMED` / `VOID` / `EXPIRED` stamp across the
page so a reprint cannot pass as live.




## OpenAPI

````yaml /api-reference/openapi.yaml get /transfer/letter/{id}/pdf
openapi: 3.1.0
info:
  title: AroPay API
  version: 1.0.0
  description: |
    The AroPay API for orchestrating sealed, private payments on public
    ledgers, built on the Aro Confidential Rails (Zama Protocol FHE). Two
    environments run the same API: Production on Ethereum Mainnet
    (`aropay.aro.media`, real funds) and the Sandbox on Sepolia
    (`dev.aropay.aro.media`, test funds and a faucet). Integrations built
    against the sandbox carry over unchanged; the faucet (`POST /fund`) is
    sandbox-only and returns 404 on production.

    Every response (except the flat Sandbox probes)
    uses the shared envelope: `{ "ok": true, "data": … }` on success and
    `{ "ok": false, "error": { "code", "message", "details?" } }` on failure.

    Amounts are decimal strings in human units (e.g. `"125.50"`). Write
    requests must send `Content-Type: application/json`.
  contact:
    name: Aro Media
    url: https://aro.media
servers:
  - url: https://aropay.aro.media/api/v1
    description: Production (Ethereum Mainnet)
  - url: https://dev.aropay.aro.media/api/v1
    description: Sandbox (Sepolia)
  - url: http://aropay.localhost:3000/api/v1
    description: Local development
security:
  - apiKeyAuth: []
  - sessionCookie: []
tags:
  - name: Auth
    description: Password, TOTP, and passkey login; session inspection and logout.
  - name: Profile & security
    description: Profile, password, two-factor authentication, and passkey management.
    x-group: Profile & security
  - name: API keys
    description: Programmatic credentials. Creation and revocation are session-only.
  - name: Wallets
    description: Custodial and external (watch-only) wallets.
  - name: Balances
    description: Balance triples and lightweight gas reads.
  - name: Money movement
    description: |
      Faucet funding, minting, transfers, and redemptions. All four are
      asynchronous: the request is validated and recorded, the response is
      `201` with the transaction in status `QUEUED` (`txHash: null`), and a
      background worker executes it. Poll `GET /transactions/{id}` until
      `status` is `CONFIRMED`, `SETTLED`, or `FAILED`. Execution failures
      surface as status `FAILED` with `errorCode` on the transaction, never
      as an HTTP error on the POST.
  - name: Aro Mail
    description: >-
      Letters of value: escrow cAROUSD, print or mail through Lob, and let the
      recipient claim by QR or code.
  - name: Transactions
    description: >-
      History, single-transaction reads (pure database reads), and notarized
      receipts.
  - name: Sandbox
    description: Unauthenticated status and echo probes with a flat response shape.
paths:
  /transfer/letter/{id}/pdf:
    get:
      tags:
        - Aro Mail
      summary: Download letter PDF
      description: |
        The printable letter: a single US Letter page with sender and
        recipient blocks, the amount panel (sealed letters print
        `••••••• cAROUSD`), the QR encoding `claimUrl`, the claim code for
        manual entry, the expiry date and a one-time-claim warning.

        Available from `ESCROWED` onwards. `DRAFT` and `FAILED` letters return
        `409 letter_not_printable`. `CLAIMED`, `VOIDED` and `EXPIRED` letters
        render with a large `CLAIMED` / `VOID` / `EXPIRED` stamp across the
        page so a reprint cannot pass as live.
      operationId: getLetterPdf
      parameters:
        - $ref: '#/components/parameters/LetterId'
      responses:
        '200':
          description: The letter as a PDF.
          headers:
            Content-Disposition:
              description: '`inline; filename="aro-mail-<id>.pdf"`'
              schema:
                type: string
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/AccountDisabled'
        '404':
          $ref: '#/components/responses/LetterNotFound'
        '409':
          description: >-
            The letter is `DRAFT` or `FAILED`; there is nothing escrowed to
            print.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                ok: false
                error:
                  code: letter_not_printable
                  message: The letter is not printable until its escrow is confirmed.
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  parameters:
    LetterId:
      name: id
      in: path
      required: true
      schema:
        type: string
      description: Letter ID.
  responses:
    Unauthorized:
      description: Missing or invalid credential.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            ok: false
            error:
              code: invalid_api_key
              message: Invalid API key.
    AccountDisabled:
      description: The account has been deactivated.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            ok: false
            error:
              code: account_disabled
              message: This account is disabled.
    LetterNotFound:
      description: >-
        No letter with this id in your account (or no letter matches the claim
        code).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            ok: false
            error:
              code: letter_not_found
              message: Letter not found.
    RateLimited:
      description: Rate limit exceeded.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            ok: false
            error:
              code: rate_limited
              message: Too many requests. Try again shortly.
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            ok: false
            error:
              code: internal_error
              message: An unexpected error occurred.
  schemas:
    ErrorResponse:
      type: object
      description: The shared failure envelope.
      required:
        - ok
        - error
      properties:
        ok:
          const: false
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code; match on this, not `message`.
            message:
              type: string
            details:
              description: Structured context, e.g. per-field validation issues.
  securitySchemes:
    apiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: aro_sk_…
      description: |
        AroPay API key, sent as `Authorization: Bearer aro_sk_…`. Created in
        the dashboard under Settings → API keys. Takes precedence over a
        session cookie when both are present.
      x-default: aro_sk_your_key_here
    sessionCookie:
      type: apiKey
      in: cookie
      name: aropay_session
      description: |
        Browser session cookie minted by `POST /auth/login` (or passkey
        login). httpOnly, `SameSite=Lax`, 24 h TTL. Endpoints marked
        **Session-only** accept only this credential and return
        `403 session_required` for API keys.

````