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

# Sweep expired letters

> **Operator-only.** Expiry and refund sweep for schedulers, meant to run
every 5 minutes: expires claimable letters whose `expiresAt` has
passed, submits their refunds, retries refunds that failed, and probes
stale receipts. Also reports vault solvency
(`vault.balance` vs. `vault.inFlight`); `vault.balance` and
`vault.solvent` are `null` when the relayer could not decrypt the
operator balance during the run.

Authenticated only by `Authorization: Bearer <AROPAY_MAIL_SWEEP_SECRET>`;
API keys and sessions are never accepted. When the deployment has no
sweep secret (or one shorter than 32 characters) the route answers
`404 not_found`; a wrong secret is `401 unauthorized`. Overlapping runs
return `409 sweep_in_progress`.

```bash
curl -X POST "$BASE/transfer/letter/sweep" \
  -H "Authorization: Bearer $AROPAY_MAIL_SWEEP_SECRET"
```




## OpenAPI

````yaml /api-reference/openapi.yaml post /transfer/letter/sweep
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/sweep:
    post:
      tags:
        - Aro Mail
      summary: Sweep expired letters
      description: >
        **Operator-only.** Expiry and refund sweep for schedulers, meant to run

        every 5 minutes: expires claimable letters whose `expiresAt` has

        passed, submits their refunds, retries refunds that failed, and probes

        stale receipts. Also reports vault solvency

        (`vault.balance` vs. `vault.inFlight`); `vault.balance` and

        `vault.solvent` are `null` when the relayer could not decrypt the

        operator balance during the run.


        Authenticated only by `Authorization: Bearer
        <AROPAY_MAIL_SWEEP_SECRET>`;

        API keys and sessions are never accepted. When the deployment has no

        sweep secret (or one shorter than 32 characters) the route answers

        `404 not_found`; a wrong secret is `401 unauthorized`. Overlapping runs

        return `409 sweep_in_progress`.


        ```bash

        curl -X POST "$BASE/transfer/letter/sweep" \
          -H "Authorization: Bearer $AROPAY_MAIL_SWEEP_SECRET"
        ```
      operationId: sweepLetters
      responses:
        '200':
          description: Sweep summary.
          content:
            application/json:
              schema:
                type: object
                required:
                  - ok
                  - data
                properties:
                  ok:
                    const: true
                  data:
                    $ref: '#/components/schemas/SweepResult'
              example:
                ok: true
                data:
                  scanned: 7
                  expired: 2
                  refundsSubmitted: 2
                  refundsFailed: 0
                  remaining: 0
                  vault:
                    balance: '12500.00'
                    inFlight: '4350.00'
                    solvent: true
        '401':
          description: Bearer token does not match the sweep secret.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                ok: false
                error:
                  code: unauthorized
                  message: Invalid sweep credentials.
        '404':
          description: >-
            The deployment has no sweep secret configured; the route does not
            exist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                ok: false
                error:
                  code: not_found
                  message: Resource not found.
        '409':
          description: A sweep is already running.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                ok: false
                error:
                  code: sweep_in_progress
                  message: A sweep is already in progress.
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - sweepSecret: []
components:
  schemas:
    SweepResult:
      type: object
      description: Outcome of one `POST /transfer/letter/sweep` run.
      required:
        - scanned
        - expired
        - refundsSubmitted
        - refundsFailed
        - remaining
        - vault
      properties:
        scanned:
          type: integer
          description: Letters examined in this run.
        expired:
          type: integer
          description: Letters flipped to `EXPIRED`.
        refundsSubmitted:
          type: integer
          description: Refund transfers submitted (new or retried).
        refundsFailed:
          type: integer
          description: Refund transfers that failed pre-submission or reverted in this run.
        remaining:
          type: integer
          description: >-
            Eligible letters left over when the time budget ran out; the next
            run picks them up.
        vault:
          type: object
          description: Escrow vault solvency at the end of the run.
          required:
            - balance
            - inFlight
            - solvent
          properties:
            balance:
              type:
                - string
                - 'null'
              description: >-
                Operator cAROUSD balance, human units. `null` when the balance
                could not be decrypted in this run (relayer unavailable).
            inFlight:
              type: string
              description: >-
                Sum of letters in `ESCROWED`/`MAILED`/`CLAIMING` plus unsettled
                refunds, human units.
            solvent:
              type:
                - boolean
                - 'null'
              description: >-
                `balance >= inFlight`; `null` when `balance` is `null` (solvency
                unknown for this run).
    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.
  responses:
    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.
    sweepSecret:
      type: http
      scheme: bearer
      description: |
        Operator-only shared secret (`AROPAY_MAIL_SWEEP_SECRET`, at least 32
        characters) presented as `Authorization: Bearer <secret>` by the
        scheduler that runs `POST /transfer/letter/sweep`. Not a user
        credential: API keys and sessions are never accepted there.

````