> ## 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.

# Preview claim

> **Public.** Look up a claim code without moving money, so the claim
page can show who the letter is from and whether it is still
claimable before the recipient signs in. Sealed letters return
`amount: null` and `memo: null`; the amount is revealed by the claim
itself.

Codes are normalised before lookup (upper-cased, dashes and whitespace
removed, `O→0`, `I→1`, `L→1`) and checksum-verified. Limited to 60
requests per 15 minutes per IP. The response never identifies the
recipient or the letter id.




## OpenAPI

````yaml /api-reference/openapi.yaml post /transfer/letter/claim/preview
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/claim/preview:
    post:
      tags:
        - Aro Mail
      summary: Preview claim
      description: |
        **Public.** Look up a claim code without moving money, so the claim
        page can show who the letter is from and whether it is still
        claimable before the recipient signs in. Sealed letters return
        `amount: null` and `memo: null`; the amount is revealed by the claim
        itself.

        Codes are normalised before lookup (upper-cased, dashes and whitespace
        removed, `O→0`, `I→1`, `L→1`) and checksum-verified. Limited to 60
        requests per 15 minutes per IP. The response never identifies the
        recipient or the letter id.
      operationId: previewLetterClaim
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClaimPreviewRequest'
            example:
              claimCode: 7K3M-Q9TX-2ABF-H8RD-5CWN-VGP4-0J6Y
      responses:
        '200':
          description: The preview.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClaimPreviewEnvelope'
              example:
                ok: true
                data:
                  preview:
                    state: claimable
                    amount: null
                    token: cAROUSD
                    amountVisibility: sealed
                    senderName: Acme Payroll, Inc.
                    memo: null
                    expiresAt: '2026-10-22T09:45:00.000Z'
                    claimedAt: null
                    chainId: 11155111
        '400':
          description: Malformed code or checksum failure.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                ok: false
                error:
                  code: invalid_claim_code
                  message: >-
                    That claim code is not valid. Check it against the letter
                    and try again.
        '404':
          $ref: '#/components/responses/LetterNotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      security: []
components:
  schemas:
    ClaimPreviewRequest:
      type: object
      required:
        - claimCode
      properties:
        claimCode:
          type: string
          minLength: 1
          maxLength: 64
          description: >-
            The 28-character code from the letter, with or without
            dashes/spaces; case-insensitive.
    ClaimPreviewEnvelope:
      type: object
      required:
        - ok
        - data
      properties:
        ok:
          const: true
        data:
          type: object
          required:
            - preview
          properties:
            preview:
              $ref: '#/components/schemas/ClaimPreview'
    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.
    ClaimPreview:
      type: object
      description: What the public claim page may show before the recipient signs in.
      required:
        - state
        - amount
        - token
        - amountVisibility
        - senderName
        - memo
        - expiresAt
        - claimedAt
        - chainId
      properties:
        state:
          type: string
          enum:
            - claimable
            - claiming
            - claimed
            - expired
            - voided
            - not_ready
            - unavailable
          description: |
            `claimable` (ESCROWED/MAILED), `claiming` (a claim is in flight),
            `claimed`, `expired`, `voided`, `not_ready` (escrow still
            confirming), `unavailable` (the escrow failed).
        amount:
          type:
            - string
            - 'null'
          description: >-
            Decimal string for `visible` letters; `null` for `sealed` letters
            until claimed.
        token:
          type: string
          examples:
            - cAROUSD
        amountVisibility:
          type: string
          enum:
            - sealed
            - visible
        senderName:
          type: string
          description: The sender's company or name; never their email.
        memo:
          type:
            - string
            - 'null'
          description: '`null` for sealed letters.'
        expiresAt:
          type: string
          format: date-time
        claimedAt:
          type:
            - string
            - 'null'
          format: date-time
        chainId:
          type: integer
  responses:
    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.
  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.

````