openapi: 3.0.0
paths:
  /v1/cards:
    post:
      description: >-
        Creates a virtual card for the given cardholder. Behavior depends on the
        `ISSUING_PROVIDER` config: Stripe issues a Stripe Issuing card; Bridge
        creates a Bridge card account funded by the supplied crypto account.
      operationId: cards_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCardBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CardResponse'
        '400':
          description: >-
            Bridge: chain + cryptoAccountAddress missing, or cardholder lacks
            completed KYC.
      summary: Issue a card
      tags:
        - cards
  /v1/cards/{id}/provisioning-token:
    post:
      description: >-
        Returns a Stripe Issuing ephemeral key suitable for Apple Pay In-App
        Provisioning, Google Pay JS push tokenization, or client-side Stripe.js
        PAN display. Stripe-only: rejects with 400 when ISSUING_PROVIDER=bridge.
        Tightly rate-limited (5/min/IP) because ephemeral keys are sensitive.
      operationId: provisioningToken
      parameters:
        - name: id
          required: true
          in: path
          description: SafeBank card UUID
          schema:
            example: 6ee0b2a0-3c8c-4f49-9d12-bd55b15f02b1
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProvisioningTokenBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProvisioningTokenResponse'
        '400':
          description: >-
            Bridge-issued card (no Stripe id), or card not found, or
            platform-mismatched fields.
      summary: Issue Stripe ephemeral key for wallet push-provisioning
      tags:
        - cards
  /v1/cards/{id}/limits:
    get:
      description: >-
        The configured per-transaction / daily / weekly / monthly caps and MCC
        rules for a card.
      operationId: getCardLimits
      parameters:
        - name: id
          required: true
          in: path
          description: SafeBank card UUID
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CardLimitsResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get card spend limits
      tags:
        - cards
    put:
      description: >-
        Sets per-transaction / daily / weekly / monthly USD caps and MCC
        block/allow rules. Only the fields sent are changed; an explicit `null`
        clears a cap or the allowlist. Enforced in real time on every
        authorization for the card.
      operationId: setCardLimits
      parameters:
        - name: id
          required: true
          in: path
          description: SafeBank card UUID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetCardLimitsBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CardLimitsResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Set card spend limits
      tags:
        - cards
  /v1/cards/{id}/freeze:
    post:
      description: >-
        Sets the card inactive at Stripe and in SafeBank — subsequent
        authorizations decline immediately.
      operationId: freezeCard
      parameters:
        - name: id
          required: true
          in: path
          description: SafeBank card UUID
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantCardResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Freeze a card
      tags:
        - cards
  /v1/cards/{id}/unfreeze:
    post:
      description: Reactivates a frozen card.
      operationId: unfreezeCard
      parameters:
        - name: id
          required: true
          in: path
          description: SafeBank card UUID
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MerchantCardResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Unfreeze a card
      tags:
        - cards
  /v1/wallets/{walletId}/cards:
    post:
      description: >-
        Issues a Safe-funded card drawing USDC from the wallet’s Safe, seeding
        its spend limits from the body. Sandbox-only (a Stripe cardholder is
        minted for the tenant on first use); live issuance requires KYB
        onboarding.
      operationId: issue
      parameters:
        - name: walletId
          required: true
          in: path
          description: Source wallet id (must be DEPLOYED).
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueCardBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IssuedCardResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Issue a card from a wallet
      tags:
        - cards
  /v1/offramp/payments:
    post:
      operationId: offrampPayments_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCorridorPaymentBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CorridorPaymentWithQuotesResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create a phone-addressed payment (routes quoted when eligible)
      tags:
        - offramp
    get:
      operationId: offrampPayments_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CorridorPaymentListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List corridor payments (sent or received)
      tags:
        - offramp
  /v1/offramp/payments/{id}:
    get:
      operationId: offrampPayments_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CorridorPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Fetch one corridor payment
      tags:
        - offramp
  /v1/offramp/payments/{id}/routes:
    get:
      operationId: routes
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RouteQuoteListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Current active route quotes
      tags:
        - offramp
  /v1/offramp/payments/{id}/select-route:
    post:
      operationId: selectRoute
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SelectRouteBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CorridorPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Select a route quote (recipient only)
      tags:
        - offramp
  /v1/offramp/payments/{id}/confirm:
    post:
      operationId: confirm
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CorridorPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Confirm the selected route — locks funds and starts execution (recipient
        only)
      tags:
        - offramp
  /v1/offramp/payments/{id}/cancel:
    post:
      operationId: cancel
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CorridorPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Cancel an unfunded payment (sender only)
      tags:
        - offramp
  /v1/claims/{claimToken}:
    get:
      operationId: view
      parameters:
        - name: claimToken
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClaimPublicResponse'
      summary: Inspect a claim link (public; the token is the authorization)
      tags:
        - claims
  /v1/claims/{claimToken}/verify-phone:
    post:
      operationId: verifyPhone
      parameters:
        - name: claimToken
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyPhoneResponse'
      summary: Send a 6-digit code to the claim's stored phone (public)
      tags:
        - claims
  /v1/claims/{claimToken}/verify-phone/confirm:
    post:
      operationId: confirmPhone
      parameters:
        - name: claimToken
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfirmPhoneBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfirmPhoneResponse'
      summary: Confirm the phone code (public; 5 attempts per challenge)
      tags:
        - claims
  /v1/claims/{claimToken}/claim:
    post:
      operationId: claim
      parameters:
        - name: claimToken
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClaimBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClaimResultResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Redeem a claim into your tenant (verified phone challenge + active
        destination)
      tags:
        - claims
  /v1/claims/{claimId}/resend-sms:
    post:
      operationId: resendSms
      parameters:
        - name: claimId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResendSmsResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Re-send the claim SMS with a fresh link (sender only, budgeted)
      tags:
        - claims
  /v1/claims/{claimId}/refund:
    post:
      operationId: refund
      parameters:
        - name: claimId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefundClaimResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Request an expired-claim refund (sender only; pre-expiry returns 409;
        idempotent)
      tags:
        - claims
  /v1/capabilities:
    get:
      description: >-
        Returns the deployment environment, the EVM chains the platform is
        configured for (with USDC token addresses), and the feature flags that
        are actually available in this deployment. Computed from config; unbuilt
        features report false.
      operationId: capabilities_get
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilitiesResponse'
      summary: Self-describing platform capabilities
      tags:
        - capabilities
  /v1/tenants:
    post:
      description: >-
        Creates the developer/business account that owns API keys and every
        API-platform resource. Authenticated with a Privy session — this is the
        one API-platform write that does not need a tenant to already exist.
        Idempotent: if the caller already owns a tenant, that tenant is
        returned.
      operationId: tenants_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTenantBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantResponse'
      security:
        - privyBearer: []
      summary: Create (bootstrap) a tenant
      tags:
        - tenants
  /v1/tenants/me:
    get:
      description: >-
        Returns the tenant the caller is acting as — resolved from the Privy
        session's membership or from the developer API key.
      operationId: me
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantResponse'
      security:
        - privyBearer: []
      summary: Get the current tenant
      tags:
        - tenants
  /v1/tenants/members:
    post:
      description: >-
        Creates a PENDING member and returns a one-time invite token (share it
        with the invitee). They accept via POST /v1/tenants/members/accept,
        registering the address they will sign with.
      operationId: invite
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InviteMemberBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InviteMemberResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Invite a member
      tags:
        - members
    get:
      operationId: members_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemberListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List members
      tags:
        - members
  /v1/tenants/members/{id}:
    delete:
      description: >-
        Marks the member REVOKED. Does NOT remove on-chain Safe ownership — the
        response lists wallets where they are still an owner so you can remove
        them with an owner-admin tx.
      operationId: remove
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveMemberResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Revoke a member
      tags:
        - members
  /v1/tenants/members/accept:
    post:
      description: >-
        Token-authenticated (no API key needed). Activates the member and
        registers the EVM address they will sign with.
      operationId: accept
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AcceptMemberBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemberResponse'
      summary: Accept an invite
      tags:
        - members
  /v1/keys:
    post:
      description: >-
        Mints a developer API key for the caller’s tenant. The plaintext key is
        returned ONCE — store it immediately. Requires the `keys:manage` scope
        (Privy sessions and root keys hold it implicitly).
      operationId: keys_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedApiKeyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create an API key
      tags:
        - keys
    get:
      description: Metadata only — never the secret.
      operationId: keys_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List API keys
      tags:
        - keys
  /v1/keys/{id}:
    delete:
      description: Soft-revokes the key (the row is kept for audit). Idempotent.
      operationId: revoke
      parameters:
        - name: id
          required: true
          in: path
          description: API key id.
          schema:
            type: string
      responses:
        '204':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Revoke an API key
      tags:
        - keys
  /v1/keys/{id}/rotate:
    post:
      description: >-
        Mints a new secret with the same scopes/environment; the old secret
        keeps working for a 24-hour grace window, then auto-expires. Returns the
        new plaintext key once.
      operationId: rotate
      parameters:
        - name: id
          required: true
          in: path
          description: API key id to rotate.
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedApiKeyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Rotate an API key
      tags:
        - keys
  /v1/agents:
    post:
      description: Creates a tenant-owned agent principal.
      operationId: agents_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create an agent
      tags:
        - agents
    get:
      description: Tenant-scoped, newest first.
      operationId: agents_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List agents
      tags:
        - agents
  /v1/agents/{id}:
    get:
      operationId: agents_get
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get an agent
      tags:
        - agents
  /v1/agents/{id}/suspend:
    post:
      description: A suspended agent is declined (AGENT_SUSPENDED) on its next action.
      operationId: suspend
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Suspend an agent
      tags:
        - agents
  /v1/agents/{id}/reactivate:
    post:
      operationId: reactivate
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Reactivate a suspended agent
      tags:
        - agents
  /v1/agents/{id}/limits:
    get:
      operationId: getLimits
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentLimitsResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get an agent’s spend limits
      tags:
        - agents
    put:
      description: >-
        Per-payment / daily / weekly / monthly USD caps enforced on every
        agent-initiated payment. Only the fields sent change; an explicit `null`
        clears a cap.
      operationId: setLimits
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetAgentLimitsBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentLimitsResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Set an agent’s spend limits
      tags:
        - agents
  /v1/agents/{id}/keys:
    post:
      description: >-
        Returns the plaintext key ONCE. Scopes are clamped to the agent ceiling
        — the key can never hold `*`, agents:manage, keys:manage, or
        policies:write.
      operationId: createKey
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgentKeyBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedApiKeyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Mint an agent-scoped API key
      tags:
        - agents
  /v1/agents/{id}/wallet:
    post:
      description: >-
        Creates a tenant-owned Safe (purpose AGENT) the agent’s card and
        payments draw from. One wallet per agent; owners are tenant signers (the
        agent holds no on-chain key). Deploys asynchronously — poll the wallet
        until DEPLOYED.
      operationId: provisionWallet
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProvisionAgentWalletBody'
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentWalletResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Provision the agent's dedicated Safe
      tags:
        - agents
  /v1/agents/{id}/cards:
    post:
      description: >-
        Issues from the agent’s dedicated wallet (provision it first). Caps
        omitted from the body are seeded from the agent’s own limits, so the
        in-prod card gate enforces the agent’s caps synchronously. Sandbox only.
      operationId: issueCard
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueAgentCardBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IssuedCardResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Issue a Safe-funded card bound to the agent
      tags:
        - agents
  /v1/agents/{id}/activity:
    get:
      description: >-
        Newest-first merge of the agent’s payments, card authorizations, and
        policy decisions. Read-only.
      operationId: getActivity
      parameters:
        - name: id
          required: true
          in: path
          description: Agent id
          schema:
            type: string
        - name: limit
          required: false
          in: query
          description: Max items (1–200). Defaults to 50.
          schema:
            example: 50
            type: number
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentActivityResponseDto'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The agent's merged audit timeline
      tags:
        - agents
  /v1/wallets:
    post:
      description: >-
        Creates a Safe for the caller’s tenant and fires the on-chain deploy
        asynchronously. Returns the predicted CREATE2 address immediately
        (usable before the proxy is mined). Defaults: Base Sepolia (84532),
        threshold 1.
      operationId: wallets_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWalletBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create a multisig wallet
      tags:
        - wallets
    get:
      description: Tenant-scoped, newest first.
      operationId: wallets_list
      parameters:
        - name: skip
          required: false
          in: query
          schema:
            type: string
        - name: take
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List wallets
      tags:
        - wallets
  /v1/wallets/pending:
    get:
      description: >-
        Pending Safe transactions across your wallets. Pass ?owner=0x… to get
        only those a specific owner has not yet signed (their co-sign queue).
      operationId: pending
      parameters:
        - name: owner
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PendingTxListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Transactions awaiting an owner’s signature
      tags:
        - wallets
  /v1/wallets/transactions/{txId}:
    get:
      operationId: getTransaction
      parameters:
        - name: txId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionDetailResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a Safe transaction (with collected signatures)
      tags:
        - wallets
  /v1/wallets/{id}:
    get:
      description: Includes live on-chain balances.
      operationId: wallets_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a wallet
      tags:
        - wallets
  /v1/wallets/{id}/transactions:
    get:
      operationId: transactions
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: skip
          required: false
          in: query
          schema:
            type: string
        - name: take
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List a wallet’s Safe transactions
      tags:
        - wallets
    post:
      description: >-
        Builds and pins a SafeTx (to/value/data). Returns the safeTxHash to
        sign.
      operationId: propose
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposeTransactionBody'
      responses:
        '201':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Propose a Safe transaction
      tags:
        - wallets
  /v1/wallets/transactions/{txId}/sign:
    post:
      description: >-
        The signer is recovered from the raw secp256k1 signature over the
        safeTxHash and must be a registered on-chain owner. When the threshold
        is met the tx becomes executable.
      operationId: sign
      parameters:
        - name: txId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignTransactionBody'
      responses:
        '201':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Submit an owner signature
      tags:
        - wallets
  /v1/wallets/transactions/{txId}/execute:
    post:
      operationId: execute
      parameters:
        - name: txId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExecuteTransactionBody'
      responses:
        '201':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Execute a threshold-met transaction on-chain
      tags:
        - wallets
  /v1/wallets/transactions/{txId}/cancel:
    post:
      operationId: wallets_cancel
      parameters:
        - name: txId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExecuteTransactionBody'
      responses:
        '201':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Cancel a pending transaction
      tags:
        - wallets
  /v1/wallets/{id}/owners:
    post:
      description: >-
        Proposes an owner-admin SafeTx; the wallet threshold of owners must sign
        it.
      operationId: addOwner
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddOwnerBody'
      responses:
        '201':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Propose adding an owner
      tags:
        - wallets
  /v1/wallets/{id}/owners/{addr}:
    delete:
      operationId: removeOwner
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: addr
          required: true
          in: path
          description: Owner address to remove
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RemoveOwnerBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Propose removing an owner
      tags:
        - wallets
    put:
      operationId: swapOwner
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: addr
          required: true
          in: path
          description: Existing owner address to replace
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SwapOwnerBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Propose swapping an owner for a new address
      tags:
        - wallets
  /v1/wallets/{id}/threshold:
    put:
      operationId: changeThreshold
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChangeThresholdBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Propose changing the signature threshold
      tags:
        - wallets
  /v1/wallets/{id}/fund:
    post:
      description: >-
        Sends platform-funded test USDC (default) or test ETH to the wallet.
        TEST environment only; capped at 1000 USDC / 0.02 ETH per drip and
        rate-limited per wallet and tenant.
      operationId: fund
      parameters:
        - name: id
          required: true
          in: path
          description: Wallet id to fund.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FundWalletBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DripResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Fund a wallet from the sandbox faucet
      tags:
        - faucet
  /v1/faucet/drips:
    get:
      description: Tenant-scoped, newest first.
      operationId: faucet_list
      parameters:
        - name: walletId
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DripListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List faucet drips
      tags:
        - faucet
  /v1/faucet/drips/{dripId}:
    get:
      operationId: faucet_get
      parameters:
        - name: dripId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DripResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a faucet drip
      tags:
        - faucet
  /v1/directory/resolve:
    get:
      description: Exact match only. Email resolution requires the target to have opted in.
      operationId: directory_resolve
      parameters:
        - name: handle
          required: false
          in: query
          schema:
            type: string
        - name: email
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HandleResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Resolve a handle or email to a destination
      tags:
        - directory
  /v1/directory/handle:
    put:
      description: Points an @handle at one of your wallets so others can pay you by name.
      operationId: directory_claim
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClaimHandleBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HandleResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Claim or update your payment handle
      tags:
        - directory
  /v1/contacts:
    post:
      operationId: contacts_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateContactBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Save a payee to the address book
      tags:
        - contacts
    get:
      operationId: contacts_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List saved contacts
      tags:
        - contacts
  /v1/contacts/{id}:
    get:
      operationId: contacts_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a contact
      tags:
        - contacts
    patch:
      operationId: update
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateContactBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Update a contact
      tags:
        - contacts
    delete:
      operationId: contacts_remove
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: Deleted.
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Delete a contact
      tags:
        - contacts
  /v1/verification-sessions:
    post:
      operationId: verificationSessions_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateVerificationSessionBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationSessionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Start a KYC or KYB verification session
      tags:
        - identity
    get:
      operationId: verificationSessions_list
      parameters:
        - name: subject_type
          required: true
          in: query
          schema:
            type: string
        - name: subject_id
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationSessionListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List verification sessions
      tags:
        - identity
  /v1/verification-sessions/{id}:
    get:
      operationId: verificationSessions_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationSessionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a verification session
      tags:
        - identity
  /v1/verification-sessions/{id}/cancel:
    post:
      operationId: verificationSessions_cancel
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationSessionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Cancel a verification session
      tags:
        - identity
  /v1/verification-sessions/{id}/simulate:
    post:
      operationId: simulate
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationSessionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Advance a sandbox session one step (TEST environment, mock provider
        only)
      tags:
        - identity
  /v1/credentials:
    get:
      operationId: credentials_list
      parameters:
        - name: subject_type
          required: true
          in: query
          schema:
            type: string
        - name: subject_id
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List credentials
      tags:
        - identity
  /v1/credentials/{id}:
    get:
      operationId: credentials_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a credential
      tags:
        - identity
  /v1/verification-sessions/{id}/handoff:
    post:
      operationId: handoff_mint
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HandoffResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Mint a one-time cross-device handoff link (rendered as a QR code)
      tags:
        - identity
  /v1/payout-destinations:
    post:
      operationId: payoutDestinations_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePayoutDestinationBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutDestinationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Save a payout destination (requires an active KYB credential)
      tags:
        - identity
    get:
      operationId: payoutDestinations_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutDestinationListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List payout destinations
      tags:
        - identity
  /v1/payout-destinations/{id}:
    get:
      operationId: payoutDestinations_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutDestinationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Fetch one payout destination
      tags:
        - identity
    patch:
      operationId: payoutDestinations_update
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePayoutDestinationBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutDestinationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Update label/priority/fallback consent (the identifier itself is
        immutable)
      tags:
        - identity
    delete:
      operationId: payoutDestinations_remove
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutDestinationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Revoke a destination (soft — history survives, routing excludes it)
      tags:
        - identity
  /v1/payout-destinations/{id}/set-default:
    post:
      operationId: setDefault
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutDestinationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Make a VERIFIED destination the default payout preference
      tags:
        - identity
  /v1/payout-destinations/{id}/verify:
    post:
      operationId: verify
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutDestinationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Verify destination ownership (sandbox only until the proof method ships)
      tags:
        - identity
  /v1/eligibility/evaluate:
    post:
      operationId: evaluate
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EvaluateEligibilityBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EligibilityResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Evaluate credential requirements for an action
      tags:
        - identity
  /v1/2fa/factors:
    get:
      operationId: listFactors
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FactorListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List your enrolled second factors
      tags:
        - 2fa
    post:
      description: >-
        Sends a confirmation code (EMAIL/SMS) or returns a TOTP secret. The
        factor stays PENDING until POST /v1/2fa/factors/{id}/verify proves
        control of the channel.
      operationId: enroll
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EnrollFactorBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EnrollFactorResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Begin enrolling a second factor
      tags:
        - 2fa
  /v1/2fa/factors/{factorId}/verify:
    post:
      operationId: twoFactor_verify
      parameters:
        - name: factorId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyFactorBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FactorResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Prove control of a channel and activate the factor
      tags:
        - 2fa
  /v1/2fa/factors/{factorId}:
    delete:
      description: >-
        Effective immediately. Refused if it would drop your usable factors
        below the tenant requirement, or leave you with only email/SMS (which
        would make your funds unreachable during a provider outage).
      operationId: twoFactor_revoke
      parameters:
        - name: factorId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Revoke a second factor
      tags:
        - 2fa
  /v1/2fa/challenges/{challengeId}:
    get:
      operationId: getChallenge
      parameters:
        - name: challengeId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Read a pending transaction challenge
      tags:
        - 2fa
  /v1/2fa/challenges/{challengeId}/send:
    post:
      description: >-
        Also used to resend. The message states the amount and destination —
        that is the anti-phishing control, so compare it against what you are
        actually sending.
      operationId: send
      parameters:
        - name: challengeId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendChallengeBody'
      responses:
        '201':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Deliver a code for one factor
      tags:
        - 2fa
  /v1/2fa/challenges/{challengeId}/verify:
    post:
      description: >-
        Repeat until satisfiedFactors reaches requiredFactors, then replay the
        original request with the X-SafeBank-2FA-Challenge header.
      operationId: verifyChallenge
      parameters:
        - name: challengeId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyChallengeBody'
      responses:
        '201':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Verify one factor against a challenge
      tags:
        - 2fa
  /v1/2fa/settings:
    get:
      operationId: getSettings
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionProtectionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Read the tenant transaction-protection settings
      tags:
        - 2fa
    patch:
      description: >-
        Owner/admin user sessions only — never an API key, whatever scopes it
        holds. The cooling-off window cannot be set below 24h while protection
        is enabled.
      operationId: updateSettings
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTransactionProtectionBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionProtectionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Update the tenant transaction-protection settings
      tags:
        - 2fa
  /v1/payments:
    post:
      description: >-
        Resolves the destination, OFAC-screens the recipient, and proposes a
        USDC transfer on the source Safe. Returns the payment with its
        safeTxHash to sign.
      operationId: payments_create
      parameters:
        - name: x-safebank-2fa-challenge
          required: false
          in: header
          description: >-
            A satisfied two-factor challenge id, replayed after a 403
            TWOFA_REQUIRED. Sent as a HEADER rather than a body field so the
            request body — and therefore the Idempotency-Key hash — is
            byte-identical between the original call and the retry.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePaymentBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create a payment
      tags:
        - payments
    get:
      description: Tenant-scoped, newest first.
      operationId: payments_list
      parameters:
        - name: skip
          required: false
          in: query
          schema:
            type: string
        - name: take
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List payments
      tags:
        - payments
  /v1/payments/{id}:
    get:
      operationId: payments_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a payment
      tags:
        - payments
  /v1/payments/{id}/sign:
    post:
      description: >-
        Submits an owner signature over the safeTxHash. Once the threshold is
        met the payment auto-executes on-chain.
      operationId: payments_sign
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignPaymentBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Sign a payment
      tags:
        - payments
  /v1/payments/{id}/execute:
    post:
      operationId: payments_execute
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExecutePaymentBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Execute a threshold-met payment on-chain
      tags:
        - payments
  /v1/payments/{id}/cancel:
    post:
      operationId: payments_cancel
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExecutePaymentBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Cancel a pending payment
      tags:
        - payments
  /v1/treasury/accounts:
    post:
      description: >-
        Creates one Safe per bucket (202 — CREATE2 addresses are usable
        immediately, deployment is async). Sandbox-only for now.
      operationId: createAccount
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTreasuryAccountBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TreasuryAccountResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create a treasury account
      tags:
        - treasury
    get:
      operationId: listAccounts
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TreasuryAccountListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List treasury accounts
      tags:
        - treasury
  /v1/treasury/accounts/{id}:
    get:
      description: >-
        Live bucket balances, drift vs targetBps, and accrued simulated yield. A
        bucket whose balance cannot be read right now reports null (never zero).
      operationId: getAccount
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TreasuryAccountResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a treasury account
      tags:
        - treasury
  /v1/treasury/accounts/{id}/rules:
    post:
      operationId: createRule
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTreasuryRuleBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TreasuryRuleResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create a rule
      tags:
        - treasury
    get:
      operationId: listRules
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TreasuryRuleListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List rules
      tags:
        - treasury
  /v1/treasury/rules/{ruleId}:
    patch:
      operationId: updateRule
      parameters:
        - name: ruleId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTreasuryRuleBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TreasuryRuleResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Update a rule (thresholds / cadence / enabled)
      tags:
        - treasury
    delete:
      operationId: deleteRule
      parameters:
        - name: ruleId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Delete a rule
      tags:
        - treasury
  /v1/treasury/accounts/{id}/sweeps:
    post:
      description: >-
        Proposes a bucket-to-bucket USDC Payment. Execution requires owner
        signatures via the payments API (a 1-of-1 Safe completes in a single
        sign call).
      operationId: sweep
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ManualSweepBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TreasurySweepResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Propose a manual sweep
      tags:
        - treasury
    get:
      description: Includes the live status of each sweep’s linked payment.
      operationId: listSweeps
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TreasurySweepListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List sweeps
      tags:
        - treasury
  /v1/ramps:
    post:
      description: >-
        on = credit the wallet Safe with USDC (fiat debit simulated; completes
        immediately). off = propose a Safe debit to the liquidation address —
        owners sign the linked payment and the (simulated) fiat payout confirms
        when it executes. Sandbox-only.
      operationId: ramps_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateRampBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RampResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create a ramp
      tags:
        - ramps
    get:
      operationId: ramps_list
      parameters:
        - name: walletId
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RampListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List ramps
      tags:
        - ramps
  /v1/ramps/{id}:
    get:
      operationId: ramps_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RampResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a ramp
      tags:
        - ramps
  /v1/policies:
    post:
      description: >-
        Compiles the document to an immutable v1 (IR + sha256) and makes it
        active.
      operationId: policies_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePolicyBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create a policy
      tags:
        - policies
    get:
      operationId: policies_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List policies
      tags:
        - policies
  /v1/policies/evaluate:
    post:
      description: >-
        Deterministically tests a hypothetical transaction against a policy (by
        id or by the policy attached to a subject) using the same engine that
        gates live authorizations. Returns allow/deny + a per-rule trace and
        writes a DecisionLog audit row.
      operationId: policies_evaluate
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EvaluatePolicyBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EvaluatePolicyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Evaluate a policy (dry-run)
      tags:
        - policies
  /v1/policies/{id}:
    get:
      operationId: policies_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get a policy
      tags:
        - policies
    delete:
      description: Archives the policy and detaches it from every subject.
      operationId: archive
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Archive a policy
      tags:
        - policies
  /v1/policies/{id}/versions:
    post:
      description: Appends an immutable version and makes it the active one.
      operationId: addVersion
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePolicyVersionBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyVersionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Commit a new version
      tags:
        - policies
    get:
      operationId: listVersions
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyVersionListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List a policy’s versions
      tags:
        - policies
  /v1/policies/{id}/attachments:
    post:
      description: >-
        Binds the policy to a wallet / card / agent / treasury account. One
        active policy per subject. Attachments scope dry-run evaluation today;
        live gating is a later increment.
      operationId: attach
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AttachPolicyBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyAttachmentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Attach a policy to a subject
      tags:
        - policies
    get:
      operationId: listAttachments
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PolicyAttachmentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List a policy’s active attachments
      tags:
        - policies
  /v1/policies/attachments/{attachmentId}:
    delete:
      operationId: detach
      parameters:
        - name: attachmentId
          required: true
          in: path
          schema:
            type: string
      responses:
        '204':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Detach a policy from a subject
      tags:
        - policies
  /v1/invoices:
    post:
      operationId: invoices_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateInvoiceBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create an invoice (draft)
      tags:
        - invoices
    get:
      operationId: invoices_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List invoices
      tags:
        - invoices
  /v1/invoices/send-to:
    post:
      description: >-
        The caller is the sender: targets the recipient by @handle or wallet
        address and carries an EIP-712 InvoiceAuthorization signed by a
        registered member key.
      operationId: sendTo
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendToInvoiceBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundInvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Send a verifiable bill to another tenant
      tags:
        - invoices
  /v1/invoices/inbox:
    get:
      operationId: inbox
      parameters:
        - name: verification
          required: false
          in: query
          schema:
            enum:
              - UNVERIFIED
              - VERIFIED
              - FAILED
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundInvoiceListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List inbound bills addressed to us
      tags:
        - invoices
  /v1/invoices/inbox/{id}:
    get:
      operationId: getInbound
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundInvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get one inbound bill
      tags:
        - invoices
  /v1/invoices/{id}:
    get:
      operationId: invoices_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get an invoice
      tags:
        - invoices
  /v1/invoices/{id}/send:
    post:
      description: >-
        Provisions the 2-of-3 hold Safe (202 — async deploy) and returns funding
        info.
      operationId: invoices_send
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Send an invoice
      tags:
        - invoices
  /v1/invoices/{id}/deliver:
    post:
      operationId: deliver
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliverInvoiceBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Mark an invoice delivered (attach evidence)
      tags:
        - invoices
  /v1/invoices/{id}/release:
    post:
      description: >-
        Proposes the release payment; two of the three owners must sign to
        execute it.
      operationId: release
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SettleInvoiceBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Release funds to the payee
      tags:
        - invoices
  /v1/invoices/{id}/refund:
    post:
      description: >-
        Proposes the refund payment; two of the three owners must sign to
        execute it.
      operationId: invoices_refund
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SettleInvoiceBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Refund funds to the payer
      tags:
        - invoices
  /v1/invoices/{id}/cancel:
    post:
      operationId: invoices_cancel
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Cancel an unfunded invoice
      tags:
        - invoices
  /v1/invoices/{id}/verify:
    post:
      operationId: invoices_verify
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundInvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Re-run verification on an inbound bill
      tags:
        - invoices
  /v1/invoices/{id}/accept:
    post:
      operationId: invoices_accept
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundInvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Accept an inbound bill (overlay)
      tags:
        - invoices
  /v1/invoices/{id}/decline:
    post:
      operationId: decline
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundInvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Decline an inbound bill (overlay)
      tags:
        - invoices
  /v1/invoices/{id}/pay:
    post:
      description: >-
        Direct payment to the payee; rides M-of-N if the source wallet is
        multi-owner.
      operationId: pay
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PayBillBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundInvoiceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Pay an inbound bill from one of our wallets
      tags:
        - invoices
  /v1/invoices/public/{id}:
    get:
      operationId: getPublic
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicInvoiceResponse'
      summary: Public invoice view (unauthenticated)
      tags:
        - invoices
  /v1/tenants/me/summary:
    get:
      operationId: summary
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantSummaryResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: 'Account summary: balance, bills owed, activity, agent spend'
      tags:
        - status
  /v1/agents/x402/authorize:
    post:
      description: >-
        Screens the payTo (OFAC), enforces the agent policy + spend caps
        (reserve-at-sign), and returns a signed `X-PAYMENT` header to retry the
        402-protected request with. The payer is the agent Safe; the
        authorization is verified on-chain (EIP-1271) and bounded by the
        SessionKeyValidator.
      operationId: authorize
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/X402AuthorizeBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/X402AuthorizeResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Authorize an x402 payment
      tags:
        - agents
  /v1/agents/x402/settle:
    post:
      description: >-
        Move a reservation from reserved to settled spend at the actual amount
        (upto ≤ ceiling) once the facilitator confirms the on-chain transfer.
        Idempotent per nonce.
      operationId: settle
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/X402SettleBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/X402SettleResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Report x402 settlement
      tags:
        - agents
info:
  title: SafeBank Infrastructure API
  description: >-
    Issuing, settlement, refunds, and on-ramp infrastructure for partner
    platforms.
  version: 1.0.0
  contact: {}
tags: []
servers: []
components:
  securitySchemes:
    appKey:
      type: apiKey
      in: header
      name: X-SafeBank-App-Key
    partnerApiKey:
      type: apiKey
      in: header
      name: X-Refund-API-Key
    adminAuth:
      type: apiKey
      in: header
      name: X-Admin-Token
    cookieAuth:
      type: apiKey
      in: cookie
      name: safebank.sid
    privyBearer:
      scheme: bearer
      bearerFormat: JWT
      type: http
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-SafeBank-Api-Key
  schemas:
    ApproveIntentDto:
      type: object
      properties: {}
    CreateKycLinkDto:
      type: object
      properties:
        fullName:
          type: string
          description: Legal name of the end-user (individual) or signatory (business).
          example: Alice Anderson
        email:
          type: string
          description: End-user email Bridge will send the hosted KYC link to.
        type:
          type: string
          description: KYC subject type. Defaults to `individual`.
          enum:
            - individual
            - business
      required:
        - fullName
        - email
    KycLinkResponse:
      type: object
      properties:
        customerId:
          type: string
          description: >-
            Bridge customer id. Persist on the partner side to link
            Bridge↔SafeBank rows.
          example: cust_abc123
        kycUrl:
          type: string
          description: Hosted KYC URL. Redirect the end-user here (or open in webview).
          example: https://kyc.bridge.xyz/...
        tosUrl:
          type: string
          description: Hosted ToS URL. Must also be completed before KYC clears.
          example: https://kyc.bridge.xyz/tos/...
        kycStatus:
          type: string
          description: KYC lifecycle status as reported by Bridge.
          example: pending
        tosStatus:
          type: string
          description: ToS-acceptance lifecycle status as reported by Bridge.
          example: pending
      required:
        - customerId
        - kycUrl
        - tosUrl
        - kycStatus
        - tosStatus
    KycStatusResponse:
      type: object
      properties:
        kycStatus:
          type: string
          description: >-
            Current KYC status. Will be `approved` once the end-user completes
            verification.
          example: pending
        tosStatus:
          type: string
          description: Current ToS-acceptance status.
          example: pending
        customerId:
          type: string
          description: Bridge customer id (echoed back for convenience).
      required:
        - kycStatus
        - tosStatus
        - customerId
    CreateOnrampDto:
      type: object
      properties:
        customerId:
          type: string
          description: Bridge customer id returned from POST /v1/bridge/kyc.
          example: cust_abc123
        destinationAddress:
          type: string
          description: >-
            Destination wallet address. Must be base58 for `solana`, EIP-55 hex
            for `world_chain`. Validated server-side against the supplied chain.
          example: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1'
        chain:
          type: string
          description: Destination chain. Restricted to Bridge card-supported chains.
          enum:
            - solana
            - world_chain
        currency:
          type: string
          description: Destination currency. Currently only `usdc` is supported.
          enum:
            - usdc
      required:
        - customerId
        - destinationAddress
        - chain
    BridgeDepositInstructions:
      type: object
      properties:
        payment_rail:
          type: string
          description: Payment rail of the deposit leg.
          example: ach_push
        currency:
          type: string
          description: Currency expected at the deposit (always `usd` for ACH).
          example: usd
        amount:
          type: string
          description: Amount the end-user wires in. Null for flexible_amount transfers.
          nullable: true
        bank_account_number:
          type: string
          description: Bank account number for ACH push.
        bank_routing_number:
          type: string
          description: Routing number for ACH push.
        bank_name:
          type: string
          description: Receiving bank name.
        bank_address:
          type: string
          description: Receiving bank address.
        deposit_message:
          type: string
          description: Message field the end-user must include with the deposit.
    BridgeTransferResponse:
      type: object
      properties:
        id:
          type: string
          description: Bridge transfer id. Use for polling and reconciliation.
        status:
          type: string
          description: Transfer lifecycle status as reported by Bridge.
          example: awaiting_funds
        on_behalf_of:
          type: string
          description: Customer id the transfer was created on behalf of.
        deposit_instructions:
          description: >-
            Onramp-only — deposit instructions the end-user follows to wire
            funds. Absent on offramp transfers.
          allOf:
            - $ref: '#/components/schemas/BridgeDepositInstructions'
        source:
          type: object
          description: Source leg as echoed by Bridge.
        destination:
          type: object
          description: Destination leg as echoed by Bridge.
      required:
        - id
        - status
    CreateOfframpDto:
      type: object
      properties:
        customerId:
          type: string
          description: Bridge customer id returned from POST /v1/bridge/kyc.
          example: cust_abc123
        amount:
          type: string
          description: USDC amount as a decimal string (≤ 6 fractional digits).
          example: '100.50'
        sourceChain:
          type: string
          description: >-
            Source chain for the USDC. Restricted to Bridge card-supported
            chains.
          enum:
            - solana
            - world_chain
        bankAccountId:
          type: string
          description: >-
            Bridge external bank account id (`external_account_id` in Bridge).
            When supplied, narrows the ACH destination to a specific bank. Omit
            to use the customer default.
      required:
        - customerId
        - amount
        - sourceChain
    ConsumerCardAttestationResponse:
      type: object
      properties:
        nonce:
          type: string
          description: >-
            Single-use nonce. Sign + return in `POST /v1/cardholders` to
            onboard.
          example: 8c5f4e91-1f2a-4d3b-9e8c-5f4e911f2a4d
        message:
          type: string
          description: >-
            Plaintext attestation message to sign (EIP-191 personal_sign).
            Format: `safetap:consumer-init:{nonce}`. The cardholder endpoint
            expects this exact prefix.
          example: safetap:consumer-init:8c5f4e91-1f2a-4d3b-9e8c-5f4e911f2a4d
        expiresAt:
          type: string
          description: ISO 8601 expiry — 5 minutes from issuance. Reuse after this fails.
          example: '2026-05-27T19:05:00.000Z'
      required:
        - nonce
        - message
        - expiresAt
    ConsumerCardholder:
      type: object
      properties:
        id:
          type: string
        walletAddress:
          type: string
        email:
          type: object
          nullable: true
          description: Optional email captured at onboarding.
        issuingProvider:
          type: string
          description: Which issuing rail backs this cardholder.
          enum:
            - stripe
            - bridge
        createdAt:
          type: string
          description: ISO 8601 onboarding timestamp.
      required:
        - id
        - walletAddress
        - email
        - issuingProvider
        - createdAt
    ConsumerCard:
      type: object
      properties:
        id:
          type: string
        last4:
          type: string
        chain:
          type: string
          nullable: true
          description: Funding chain for Bridge-issued cards. Null for Stripe Issuing.
          enum:
            - solana
            - world_chain
        issuingProvider:
          type: string
          enum:
            - stripe
            - bridge
        createdAt:
          type: string
      required:
        - id
        - last4
        - chain
        - issuingProvider
        - createdAt
    ConsumerBalance:
      type: object
      properties:
        spendBalanceUsd:
          type: string
          description: Available spend balance in USD (Decimal-backed string).
        reservedUsd:
          type: string
          description: Reserved (pending-auth) amount in USD.
        dailySpentUsd:
          type: string
          description: USD spent today.
        dayKey:
          type: string
          description: >-
            YYYY-MM-DD bucket the dailySpentUsd is scoped to. Rolls over at UTC
            midnight.
          example: '2026-05-27'
      required:
        - spendBalanceUsd
        - reservedUsd
        - dailySpentUsd
        - dayKey
    ConsumerSpendingPolicy:
      type: object
      properties:
        maxPerTxnUsd:
          type: string
          description: Maximum allowed per single authorization (USD).
        dailyCapUsd:
          type: string
          description: Daily spending cap (USD).
      required:
        - maxPerTxnUsd
        - dailyCapUsd
    ConsumerProfileResponse:
      type: object
      properties:
        cardholder:
          $ref: '#/components/schemas/ConsumerCardholder'
        cards:
          type: array
          items:
            $ref: '#/components/schemas/ConsumerCard'
        balance:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/ConsumerBalance'
        spendingPolicy:
          nullable: true
          description: Null when the cardholder has not yet had a policy provisioned.
          type: object
          allOf:
            - $ref: '#/components/schemas/ConsumerSpendingPolicy'
      required:
        - cardholder
        - cards
        - balance
        - spendingPolicy
    ConsumerTransactionRow:
      type: object
      properties:
        id:
          type: string
        amount:
          type: string
          description: Authorization amount as a decimal string (currency-native units).
        currency:
          type: string
          example: USD
        mcc:
          type: object
          nullable: true
          description: MCC code if reported by the issuer.
        category:
          type: string
          description: Resolved category label (e.g. "groceries", "restaurants").
        categoryLabel:
          type: string
          description: Human-readable category label for UI display.
        categoryColor:
          type: string
          description: CSS color token for category badge.
        decision:
          type: string
          description: Authorization decision (approved | declined | reversed | closed).
        createdAt:
          type: string
          description: ISO 8601 authorization timestamp.
      required:
        - id
        - amount
        - currency
        - mcc
        - category
        - categoryLabel
        - categoryColor
        - decision
        - createdAt
    ConsumerTransactionsResponse:
      type: object
      properties:
        transactions:
          type: array
          items:
            $ref: '#/components/schemas/ConsumerTransactionRow'
        total:
          type: number
          description: Total count matching the (optionally-filtered) query.
      required:
        - transactions
        - total
    AdminDemoMintDto:
      type: object
      properties: {}
    CreateTapTxResponse:
      type: object
      properties:
        txId:
          type: string
          description: SafetapTransaction UUID. Embed in merchant POS URL/QR.
          example: 11111111-2222-3333-4444-555555555555
        url:
          type: string
          description: Customer-facing safetap.io URL including HMAC sig query param.
          example: https://safetap.io/m/{merchantId}/t/{txId}?sig=...
        sig:
          type: string
          description: HMAC signature over `txId` — anti-enumeration token.
        status:
          type: string
          description: Initial lifecycle status (always AWAITING_TAP for new transactions).
          enum:
            - CREATED
            - AWAITING_TAP
            - PREAUTH_APPROVED
            - ONCHAIN_DISPATCHED
            - FINALIZED
            - EXPIRED
          example: AWAITING_TAP
      required:
        - txId
        - url
        - sig
        - status
    CleanupBodyDto:
      type: object
      properties: {}
    ForceStatusBodyDto:
      type: object
      properties: {}
    OutboxStatusCounts:
      type: object
      properties:
        PENDING:
          type: number
          description: Rows awaiting worker claim.
          example: 7
        PROCESSING:
          type: number
          description: >-
            Rows currently claimed by a worker. Stuck if held past the lease
            window.
          example: 1
        DONE:
          type: number
          description: Successfully dispatched rows.
          example: 1429
        FAILED:
          type: number
          description: Terminal failures after exhausting retries.
          example: 0
      required:
        - PENDING
        - PROCESSING
        - DONE
        - FAILED
    OutboxStatusResponse:
      type: object
      properties:
        counts:
          $ref: '#/components/schemas/OutboxStatusCounts'
        oldestPendingAgeSeconds:
          type: object
          description: >-
            Wall-clock age in seconds of the oldest PENDING row. Null when no
            rows are pending.
          example: 12
          nullable: true
        oldestPendingId:
          type: object
          description: CUID of the oldest PENDING row, for `prisma studio` follow-up.
          example: cl0xabc...
          nullable: true
        oldestProcessingAgeSeconds:
          type: object
          description: >-
            Wall-clock age of the longest-running PROCESSING row. Anything past
            the configured lease window is a stuck/zombie row needing operator
            action.
          example: 3
          nullable: true
        oldestProcessingId:
          type: object
          description: CUID of the oldest PROCESSING row.
          example: cl0xdef...
          nullable: true
      required:
        - counts
        - oldestPendingAgeSeconds
        - oldestPendingId
        - oldestProcessingAgeSeconds
        - oldestProcessingId
    ReconBody:
      type: object
      properties: {}
    CreatePassRequestDto:
      type: object
      properties: {}
    CreateTapTxDto:
      type: object
      properties:
        merchantId:
          type: string
          description: Merchant UUID who is ringing up the customer.
          example: 550e8400-e29b-41d4-a716-446655440000
        amountUsd:
          type: string
          description: >-
            USD amount as Decimal string. ≤ 6 fractional digits (USDC
            precision).
          example: '10.49'
      required:
        - merchantId
        - amountUsd
    PublicTapTxResponse:
      type: object
      properties:
        id:
          type: string
        merchantId:
          type: string
        amountUsd:
          type: string
          description: USD amount (Decimal string).
          example: '10.49'
        merchantName:
          type: string
          description: Merchant display name shown to the customer.
        status:
          type: string
          description: >-
            Lifecycle status. Auto-flips to EXPIRED once `expiresAt` elapses on
            a stale AWAITING_TAP row at read time.
          enum:
            - CREATED
            - AWAITING_TAP
            - PREAUTH_APPROVED
            - ONCHAIN_DISPATCHED
            - FINALIZED
            - EXPIRED
        expiresAt:
          type: object
          nullable: true
          description: ISO 8601 expiry. Null = no expiry.
      required:
        - id
        - merchantId
        - amountUsd
        - merchantName
        - status
        - expiresAt
    PatchTapTxStateDto:
      type: object
      properties:
        status:
          type: string
          description: New lifecycle status. Only forward transitions are accepted.
          enum:
            - CREATED
            - AWAITING_TAP
            - PREAUTH_APPROVED
            - ONCHAIN_DISPATCHED
            - FINALIZED
            - EXPIRED
        txHash:
          type: string
          description: >-
            On-chain transaction hash. Set when transitioning to
            ONCHAIN_DISPATCHED.
      required:
        - status
    TapTxStateAck:
      type: object
      properties:
        ok:
          type: boolean
          example: true
      required:
        - ok
    CreateCardholderBody:
      type: object
      properties:
        walletAddress:
          type: string
          description: EIP-55 Ethereum address that signed the consumer-init attestation.
          example: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1'
        signature:
          type: string
          description: >-
            Hex-encoded ECDSA signature over `message`. Recovered address must
            match `walletAddress`.
          example: 0xabc123...
        message:
          type: string
          description: >-
            Plaintext attestation message. For consumer init flows, format is
            `safetap:consumer-init:{nonce}:{checksumAddress}` where `nonce` was
            issued by the prior nonce endpoint.
          example: safetap:consumer-init:abc123:0x742d35...
        email:
          type: string
          description: Optional contact email for receipts + KYC follow-up.
          example: alice@example.com
        fundingChain:
          type: string
          description: >-
            Bridge only — override the default funding chain for this
            cardholder.
          enum:
            - solana
            - world_chain
        safetapTxId:
          type: string
          description: >-
            Binds this cardholder to a specific SafetapTransaction at onboarding
            so issuing webhooks can route future auths to the right merchant
            without relying on the global findActiveTx() race-prone fallback.
            UUID v4.
          example: 11111111-2222-3333-4444-555555555555
      required:
        - walletAddress
        - signature
        - message
    CardholderResponse:
      type: object
      properties:
        cardholderId:
          type: string
          description: >-
            SafeBank cardholder UUID. Stable identifier for downstream POST
            /v1/cards.
          example: 550e8400-e29b-41d4-a716-446655440000
        stripeCardholderId:
          type: string
          description: >-
            Stripe Issuing cardholder id (only set when issuing-provider is
            Stripe).
          example: ich_1NXxxx
        issuingProvider:
          type: string
          description: >-
            Issuing provider that backed this cardholder. Present on the Bridge
            path.
          enum:
            - stripe
            - bridge
          example: bridge
        idempotent:
          type: boolean
          description: >-
            True when this wallet was already known and the existing cardholder
            row was returned unchanged. Safe-to-retry indicator — partners can
            re-POST without creating duplicates.
          example: false
      required:
        - cardholderId
        - idempotent
    CreateCardBody:
      type: object
      properties:
        cardholderId:
          type: string
          description: Cardholder UUID returned from POST /v1/cardholders.
          example: 550e8400-e29b-41d4-a716-446655440000
        chain:
          type: string
          description: Bridge only — blockchain for the card account funding source.
          enum:
            - solana
            - world_chain
            - base
        cryptoAccountAddress:
          type: string
          description: Bridge only — crypto account address that funds card draws.
          example: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1'
        cryptoAccountType:
          type: string
          description: Bridge only — crypto account type. Defaults to `standard`.
          enum:
            - standard
            - bridge_wallet
      required:
        - cardholderId
    CardResponse:
      type: object
      properties:
        cardId:
          type: string
          description: >-
            SafeBank card UUID. Use this id for downstream calls (e.g.
            provisioning-token).
          example: 6ee0b2a0-3c8c-4f49-9d12-bd55b15f02b1
        stripeCardId:
          type: string
          description: Stripe Issuing card id — set when the card was issued via Stripe.
          example: ic_1NXxxx
        bridgeCardAccountId:
          type: string
          description: Bridge card account id — set when the card was issued via Bridge.
          example: card_acc_abc123
        last4:
          type: string
          description: Last four digits of the issued card.
          example: '4242'
      required:
        - cardId
        - last4
    ProvisioningTokenBody:
      type: object
      properties:
        platform:
          type: string
          description: >-
            Target wallet vendor. `apple` requires Apple Pay In-App Provisioning
            fields.
          enum:
            - apple
            - google
        nonce:
          type: string
          description: >-
            Required for Apple/Google Pay In-App Provisioning push tokenization.
            Omit for client-side card display via Stripe.js (PAN reveal).
        nonceSignature:
          type: string
          description: >-
            Apple Pay only — base64 signature over `nonce` produced by
            PKAddPaymentPassRequest. Required when `platform=apple` AND `nonce`
            is set; omit with nonce for the desktop / dry-run ephemeral-key
            path.
        certificates:
          type: array
          description: >-
            Apple Pay only — array of base64 certificates from
            PKAddPaymentPassRequest. Required when `platform=apple` AND `nonce`
            is set.
          items:
            type: string
      required:
        - platform
    StripeEphemeralKey:
      type: object
      properties:
        id:
          type: string
          example: ephkey_1NX...
        secret:
          type: string
          description: >-
            Single-use secret. Pass to Apple/Google Wallet push-provisioning SDK
            on device.
          example: ek_test_...
        expires:
          type: number
          description: >-
            UNIX timestamp (seconds) at which the ephemeral key stops being
            valid.
          example: 1715000000
        associated_objects:
          type: array
          description: >-
            Stripe-shaped associated objects array. Forwarded verbatim from
            Stripe.
          items:
            type: object
            properties:
              id:
                type: string
              type:
                type: string
      required:
        - id
        - secret
        - expires
    ProvisioningTokenResponse:
      type: object
      properties:
        cardId:
          type: string
          example: 6ee0b2a0-3c8c-4f49-9d12-bd55b15f02b1
        stripeCardId:
          type: string
          example: ic_1NXxxx
        platform:
          type: string
          enum:
            - apple
            - google
        ephemeralKey:
          $ref: '#/components/schemas/StripeEphemeralKey'
      required:
        - cardId
        - stripeCardId
        - platform
        - ephemeralKey
    CardLimitsResponse:
      type: object
      properties:
        cardId:
          type: string
          description: SafeBank card UUID.
          example: 6ee0b2a0-3c8c-4f49-9d12-bd55b15f02b1
        perTransactionUsd:
          type: object
          description: Per-transaction USD cap, or null when unlimited.
          example: '250.00'
          nullable: true
        dailyUsd:
          type: object
          description: Rolling 24h USD cap, or null.
          example: '500.00'
          nullable: true
        weeklyUsd:
          type: object
          description: Rolling ISO-week USD cap, or null.
          example: '2000.00'
          nullable: true
        monthlyUsd:
          type: object
          description: Calendar-month USD cap, or null.
          example: '5000.00'
          nullable: true
        blockedMcc:
          type: array
          description: MCCs declined for this card.
          items:
            type: string
          example: []
        allowedMcc:
          type: array
          description: Allowlisted MCCs, or null when there is no allowlist.
          items:
            type: string
          example: null
          nullable: true
      required:
        - cardId
        - perTransactionUsd
        - dailyUsd
        - weeklyUsd
        - monthlyUsd
        - blockedMcc
        - allowedMcc
    SetCardLimitsBody:
      type: object
      properties:
        perTransactionUsd:
          type: object
          description: Per-transaction USD cap. `null` clears it.
          example: '250.00'
          nullable: true
        dailyUsd:
          type: object
          description: Rolling 24h USD cap. `null` clears it.
          example: '500.00'
          nullable: true
        weeklyUsd:
          type: object
          description: Rolling ISO-week USD cap. `null` clears it.
          example: '2000.00'
          nullable: true
        monthlyUsd:
          type: object
          description: Calendar-month USD cap. `null` clears it.
          example: '5000.00'
          nullable: true
        blockedMcc:
          type: array
          description: MCCs to decline. Replaces the existing list.
          items:
            type: string
          example:
            - '7995'
        allowedMcc:
          type: array
          description: >-
            When set, only these MCCs are allowed. `null` clears the allowlist
            (allow anything not blocked).
          items:
            type: string
          example:
            - '5411'
            - '5812'
          nullable: true
    MerchantCardResponse:
      type: object
      properties:
        cardId:
          type: string
          description: SafeBank card UUID.
          example: 6ee0b2a0-3c8c-4f49-9d12-bd55b15f02b1
        status:
          type: string
          description: Card status. `inactive` = frozen.
          enum:
            - active
            - inactive
            - canceled
          example: inactive
        last4:
          type: string
          description: Last four digits.
          example: '4242'
        safeAddress:
          type: string
          description: The Safe the card draws USDC from.
          example: '0x1111111111111111111111111111111111111111'
      required:
        - cardId
        - status
        - last4
        - safeAddress
    IssueCardBody:
      type: object
      properties:
        perTransactionUsd:
          type: object
          description: Per-transaction USD cap. `null` clears it.
          example: '250.00'
          nullable: true
        dailyUsd:
          type: object
          description: Rolling 24h USD cap. `null` clears it.
          example: '500.00'
          nullable: true
        weeklyUsd:
          type: object
          description: Rolling ISO-week USD cap. `null` clears it.
          example: '2000.00'
          nullable: true
        monthlyUsd:
          type: object
          description: Calendar-month USD cap. `null` clears it.
          example: '5000.00'
          nullable: true
        blockedMcc:
          type: array
          description: MCCs to decline. Replaces the existing list.
          items:
            type: string
          example:
            - '7995'
        allowedMcc:
          type: array
          description: >-
            When set, only these MCCs are allowed. `null` clears the allowlist
            (allow anything not blocked).
          items:
            type: string
          example:
            - '5411'
            - '5812'
          nullable: true
        agentId:
          type: string
          description: >-
            Bind the card to an agent (its spend also accumulates under the
            agent).
          example: a1b2c3d4-5e6f-7890-abcd-ef1234567890
    IssuedCardResponse:
      type: object
      properties:
        cardId:
          type: string
          description: SafeBank card UUID.
          example: 6ee0b2a0-3c8c-4f49-9d12-bd55b15f02b1
        stripeCardId:
          type: object
          description: Stripe Issuing card id.
          example: ic_1NXxxx
          nullable: true
        last4:
          type: string
          description: Last four digits.
          example: '4242'
        safeAddress:
          type: string
          description: The Safe the card draws USDC from.
          example: '0x1111111111111111111111111111111111111111'
        status:
          type: string
          description: Card status.
          example: active
      required:
        - cardId
        - stripeCardId
        - last4
        - safeAddress
        - status
    ConnectOnboardingResponse:
      type: object
      properties:
        stripeAccountId:
          type: string
          description: Stripe Connect Standard account id (e.g. `acct_1AbC...`).
          example: acct_1AbCdEf2GhIjKlMn
        hostedOnboardingUrl:
          type: string
          description: >-
            Stripe-hosted onboarding URL. Redirect the merchant here to complete
            KYC + bank verification. Single-use; expires after `expiresAt`. If
            the merchant abandons, call POST
            /v1/merchants/:id/connect/onboarding again to mint a fresh link.
          example: https://connect.stripe.com/setup/s/...
        expiresAt:
          type: number
          description: Unix seconds when the onboarding URL expires (Stripe-supplied).
          example: 1715000000
        idempotent:
          type: boolean
          description: >-
            True when this call returned an EXISTING Connect account rather than
            minting a new one. Safe-to-retry indicator — partners can re-POST
            without creating duplicates.
          example: false
      required:
        - stripeAccountId
        - hostedOnboardingUrl
        - expiresAt
        - idempotent
    ConnectStatusResponse:
      type: object
      properties:
        stripeAccountId:
          type: object
          description: Stripe Connect account id. Null until POST /onboarding is called.
          nullable: true
        chargesEnabled:
          type: boolean
          description: >-
            Stripe `charges_enabled` flag on the connected account. Required
            before settlement-to-bank can flow. Kept fresh by the
            `account.updated` webhook (Sprint 25).
        payoutsEnabled:
          type: boolean
          description: Stripe `payouts_enabled` flag.
        onboardingComplete:
          type: boolean
          description: >-
            Convenience flag — true when Stripe reports both `details_submitted`
            and an empty `requirements.currently_due`. Use as the "merchant is
            fully onboarded" gate.
      required:
        - stripeAccountId
        - chargesEnabled
        - payoutsEnabled
        - onboardingComplete
    DisbursementResponse:
      type: object
      properties:
        disbursementId:
          type: string
          description: Disbursement UUID.
          example: d2e1f3a0-...
        merchantId:
          type: string
          description: Echo of the merchant id.
        amountUsdcBaseUnits:
          type: string
          description: Sum of un-disbursed merchant slices, base-units (1e-6 USDC) string.
          example: '50000000'
        amountUsdCents:
          type: number
          description: USD cents shipped to Stripe (`amountUsdcBaseUnits / 10000`).
          example: 5000
        stripeTransferId:
          type: object
          description: >-
            Stripe `tr_*` id once the Transfer succeeds. Null when
            status=PENDING/FAILED.
          nullable: true
        status:
          type: string
          description: >-
            Lifecycle status. Sprint 26 ships PENDING/SUBMITTED/FAILED
            transitions; CONFIRMED is wired in a future sprint via the
            `transfer.updated` webhook.
          enum:
            - PENDING
            - SUBMITTED
            - CONFIRMED
            - FAILED
          example: SUBMITTED
        settlementSplitCount:
          type: number
          description: Count of SettlementSplit rows bundled into this disbursement.
          example: 3
        createdAt:
          type: string
          description: ISO 8601 disbursement creation timestamp.
      required:
        - disbursementId
        - merchantId
        - amountUsdcBaseUnits
        - amountUsdCents
        - status
        - settlementSplitCount
        - createdAt
    RegEContactInfo:
      type: object
      properties:
        email:
          type: string
          example: disputes@safebank.example.com
        phone:
          type: string
          example: +1-800-555-0199
        mailingAddress:
          type: string
          example: SafeBank Disputes, P.O. Box 12345, San Francisco CA 94104
      required:
        - email
        - phone
    RegETimingInfo:
      type: object
      properties:
        errorReportingWindowDays:
          type: number
          description: >-
            Number of business days the consumer has to report an error after
            the periodic statement.
          example: 60
        investigationDays:
          type: number
          description: >-
            Number of business days the issuer has to complete investigation (or
            issue provisional credit).
          example: 10
        investigationCompleteDays:
          type: number
          description: >-
            Number of days for completed investigation (extended for
            foreign/POS).
          example: 45
        liabilityIfReportedWithin2Days:
          type: string
          description: >-
            Maximum consumer liability when reported within 2 business days.
            USD.
          example: '50.00'
        liabilityIfReportedWithin60Days:
          type: string
          description: Maximum consumer liability when reported within 60 days. USD.
          example: '500.00'
      required:
        - errorReportingWindowDays
        - investigationDays
        - investigationCompleteDays
        - liabilityIfReportedWithin2Days
        - liabilityIfReportedWithin60Days
    RegEDisclosureResponse:
      type: object
      properties:
        version:
          type: string
          description: >-
            Disclosure version. Increments when SafeBank materially revises the
            disclosure text.
          example: '1.0'
        effectiveDate:
          type: string
          description: ISO 8601 date this disclosure version became effective.
          example: '2026-05-27'
        body:
          type: string
          description: >-
            Full disclosure body (plain text, Markdown-friendly). Designed for
            direct consumer rendering.
        contact:
          $ref: '#/components/schemas/RegEContactInfo'
        timing:
          $ref: '#/components/schemas/RegETimingInfo'
      required:
        - version
        - effectiveDate
        - body
        - contact
        - timing
    CreateDisputeBody:
      type: object
      properties:
        issuingAuthorizationId:
          type: string
          description: IssuingAuthorization UUID being disputed.
          example: 11111111-2222-3333-4444-555555555555
        reason:
          type: string
          description: Reg E categorization of the dispute.
          enum:
            - UNAUTHORIZED
            - DUPLICATE
            - INCORRECT_AMOUNT
            - MERCHANDISE_NOT_RECEIVED
            - OTHER
        description:
          type: string
          description: >-
            Consumer-supplied narrative describing the issue. Required by the
            investigation team. Bounded at the DTO level so the field stays
            readable in the case file.
          example: I did not authorize this charge. Card was in my wallet at the time.
          minLength: 10
          maxLength: 2000
      required:
        - issuingAuthorizationId
        - reason
        - description
    DisputeResponse:
      type: object
      properties:
        id:
          type: string
          description: Dispute UUID.
          example: d1...
        issuingAuthorizationId:
          type: string
          description: Echo of the disputed IssuingAuthorization id.
        reason:
          type: string
          enum:
            - UNAUTHORIZED
            - DUPLICATE
            - INCORRECT_AMOUNT
            - MERCHANDISE_NOT_RECEIVED
            - OTHER
        description:
          type: string
        status:
          type: string
          enum:
            - REPORTED
            - INVESTIGATING
            - PROVISIONAL_CREDIT_ISSUED
            - RESOLVED_FAVOR_CONSUMER
            - RESOLVED_FAVOR_MERCHANT
            - WITHDRAWN
        amountDisputed:
          type: string
          description: USD amount under dispute (Decimal string).
          example: '49.99'
        provisionalCreditAmount:
          type: object
          description: >-
            USD provisionally credited (Sprint 29+). Null until the 10-day clock
            elapses.
          nullable: true
        provisionalCreditIssuedAt:
          type: object
          description: ISO 8601 timestamp when provisional credit was issued.
          nullable: true
        resolvedAt:
          type: object
          description: ISO 8601 timestamp when status transitioned to a RESOLVED_* state.
          nullable: true
        resolutionNotes:
          type: object
          description: >-
            Ops-supplied resolution explanation. Reg E mandates written notice
            within 3 BD.
          nullable: true
        createdAt:
          type: string
          description: ISO 8601 dispute creation timestamp.
        updatedAt:
          type: string
          description: ISO 8601 last status update timestamp.
      required:
        - id
        - issuingAuthorizationId
        - reason
        - description
        - status
        - amountDisputed
        - createdAt
        - updatedAt
    DisputeListResponse:
      type: object
      properties:
        disputes:
          type: array
          items:
            $ref: '#/components/schemas/DisputeResponse'
        total:
          type: number
          description: Total count for this consumer.
          example: 3
      required:
        - disputes
        - total
    DisputeNoticeResponse:
      type: object
      properties:
        id:
          type: string
          description: Notice UUID.
        noticeType:
          type: string
          description: Which lifecycle event generated this notice.
          enum:
            - PROVISIONAL_CREDIT
            - RESOLUTION_FAVOR_CONSUMER
            - RESOLUTION_FAVOR_MERCHANT
        body:
          type: string
          description: Rendered notice text (Markdown-friendly plain text).
        generatedAt:
          type: string
          description: ISO 8601 timestamp the notice was generated.
        deliveredAt:
          type: object
          description: >-
            ISO 8601 timestamp the notice was delivered. Null until delivery is
            wired.
          nullable: true
        deliveryChannel:
          type: object
          description: >-
            Channel the notice was delivered over ('email' | 'sms' | 'portal').
            Null until delivered.
          nullable: true
      required:
        - id
        - noticeType
        - body
        - generatedAt
        - deliveredAt
        - deliveryChannel
    DisputeNoticeListResponse:
      type: object
      properties:
        notices:
          type: array
          items:
            $ref: '#/components/schemas/DisputeNoticeResponse'
        total:
          type: number
          description: Total notices generated for this dispute.
          example: 2
      required:
        - notices
        - total
    ResolveDisputeBody:
      type: object
      properties:
        favor:
          type: string
          description: >-
            Binary outcome. FAVOR_CONSUMER keeps any provisional credit and
            makes it permanent (or issues credit if none was provisional);
            FAVOR_MERCHANT reverses any provisional credit and the consumer
            remains liable.
          enum:
            - FAVOR_CONSUMER
            - FAVOR_MERCHANT
        notes:
          type: string
          description: >-
            Ops-supplied resolution explanation. Reg E mandates written notice
            to the consumer within 3 business days; this text is the basis for
            that notice.
          minLength: 10
          maxLength: 2000
          example: >-
            Merchant provided proof of delivery and signed receipt. Dispute
            denied.
      required:
        - favor
        - notes
    ClockRunResponse:
      type: object
      properties:
        dueFound:
          type: number
          description: Disputes found past the 10-business-day threshold this run.
        processed:
          type: number
          description: Disputes successfully provisionally-credited this run.
      required:
        - dueFound
        - processed
    LivenessResponse:
      type: object
      properties:
        status:
          type: string
          description: Always literal "ok" when the endpoint returns at all.
          example: ok
          enum:
            - ok
      required:
        - status
    ReadinessChecks:
      type: object
      properties:
        db:
          type: string
          description: Postgres connectivity check (`SELECT 1`).
          example: ok
          enum:
            - ok
            - fail
      required:
        - db
    ReadinessResponse:
      type: object
      properties:
        status:
          type: string
          description: '`ok` → 200; `degraded` → 503 + at least one failing check.'
          example: ok
          enum:
            - ok
            - degraded
        checks:
          $ref: '#/components/schemas/ReadinessChecks'
      required:
        - status
        - checks
    MeRolesResponse:
      type: object
      properties: {}
    PartnerStreamTicketResponse:
      type: object
      properties:
        ticket:
          type: string
          description: >-
            Short-lived signed ticket bound to (partnerId, expiry). Pass as
            `?ticket=` on `GET :id/stream`.
          example: v1.<partnerId>.<expiresAtMs>.<hmacSha256Hex>
        expiresAt:
          type: string
          description: ISO 8601 expiry of the ticket (default TTL 60s).
      required:
        - ticket
        - expiresAt
    PartnerPayoutsTotals:
      type: object
      properties:
        accruedPartnerUsdc:
          type: string
          description: Total partner-rev-share accrual (USDC base-units string).
        accruedMerchantUsdc:
          type: string
          description: Total merchant net settlement (USDC base-units string).
        accruedSafebankUsdc:
          type: string
          description: Total SafeBank fee captured (USDC base-units string).
        grossUsdc:
          type: string
          description: Total gross settlement volume (USDC base-units string).
        decimals:
          type: number
          description: USDC decimal places (always 6).
          example: 6
      required:
        - accruedPartnerUsdc
        - accruedMerchantUsdc
        - accruedSafebankUsdc
        - grossUsdc
        - decimals
    PartnerMerchantSummary:
      type: object
      properties:
        id:
          type: string
        businessName:
          type: string
        feeBps:
          type: object
          description: >-
            Per-merchant fee override in basis points. Null when the merchant
            inherits the partner-level default rev-share.
          nullable: true
      required:
        - id
        - businessName
        - feeBps
    PartnerPerMerchantTotals:
      type: object
      properties:
        merchantId:
          type: string
          description: Merchant id (or "unknown" for orphaned rows).
        merchantName:
          type: object
          nullable: true
        settlementCount:
          type: number
        partnerUsdc:
          type: string
        grossUsdc:
          type: string
      required:
        - merchantId
        - merchantName
        - settlementCount
        - partnerUsdc
        - grossUsdc
    PartnerPayoutsResponse:
      type: object
      properties:
        partnerId:
          type: string
        name:
          type: string
        treasuryAddress:
          type: string
          description: >-
            On-chain treasury address that receives the partner rev-share at
            settlement.
          example: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1'
        defaultRevShareBps:
          type: number
          description: Default rev-share basis points applied to new merchants.
        merchantCount:
          type: number
        settlementCount:
          type: number
        lastSettlementAt:
          type: object
          description: ISO 8601 timestamp of the most recent settlement under this partner.
          nullable: true
        totals:
          $ref: '#/components/schemas/PartnerPayoutsTotals'
        paidUsdc:
          type: string
          description: >-
            On-chain payout to the partner treasury. Equals accruedPartnerUsdc
            for atomic pullAndSplit settlements (current architecture).
        merchants:
          type: array
          items:
            $ref: '#/components/schemas/PartnerMerchantSummary'
        perMerchant:
          type: array
          items:
            $ref: '#/components/schemas/PartnerPerMerchantTotals'
      required:
        - partnerId
        - name
        - treasuryAddress
        - defaultRevShareBps
        - merchantCount
        - settlementCount
        - lastSettlementAt
        - totals
        - paidUsdc
        - merchants
        - perMerchant
    PartnerPayoutDetailItem:
      type: object
      properties:
        id:
          type: string
          description: SettlementSplit row id; use as cursor for the next page.
        txHash:
          type: string
          description: On-chain settlement transaction hash.
        merchantId:
          type: object
          nullable: true
        merchantName:
          type: object
          nullable: true
        grossUsdc:
          type: string
        merchantUsdc:
          type: string
        safebankUsdc:
          type: string
        partnerUsdc:
          type: string
        feeBps:
          type: number
        partnerRevShareBps:
          type: number
        payerWalletAddress:
          type: object
          nullable: true
          description: Payer wallet address (when known).
        createdAt:
          type: string
          description: ISO 8601 settlement timestamp.
      required:
        - id
        - txHash
        - merchantId
        - merchantName
        - grossUsdc
        - merchantUsdc
        - safebankUsdc
        - partnerUsdc
        - feeBps
        - partnerRevShareBps
        - payerWalletAddress
        - createdAt
    PartnerPayoutDetailsResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/PartnerPayoutDetailItem'
        nextCursor:
          type: object
          description: >-
            Pass as `?cursor=` to fetch the next page. Null when the current
            page is the last.
          nullable: true
      required:
        - items
    CreateCorridorPaymentBody:
      type: object
      properties:
        phone:
          type: string
          example: '+15551234567'
          description: Recipient phone (E.164).
        amountUsd:
          type: string
          example: '25.00'
          description: USD amount (≤6 fraction digits).
        chainId:
          type: number
          description: Source chain id (default 84532).
        fundingMode:
          type: string
          enum:
            - INTERNAL_BALANCE
            - ONCHAIN_DEPOSIT
            - SAFE_PROPOSAL
          description: 'How the sender funds the payment. v1: INTERNAL_BALANCE.'
        requestedDestinationId:
          type: string
          description: >-
            One of the RECIPIENT's active destination ids (server-verified
            ownership).
        speedPreference:
          type: string
          enum:
            - FASTEST
            - CHEAPEST
      required:
        - phone
        - amountUsd
    RouteQuoteResponse:
      type: object
      properties:
        routeId:
          type: string
        paymentId:
          type: string
        destinationId:
          type: string
        provider:
          type: string
        sourceAmountBaseUnits:
          type: string
        sourceAssetId:
          type: string
        destinationAmountMinor:
          type: string
        destinationCurrency:
          type: string
        fees:
          type: object
          description: Itemized fees (minor units).
        exchangeRate:
          type: string
        estimatedDeliverySeconds:
          type: object
        requiresBridge:
          type: boolean
        requiresConversion:
          type: boolean
        requiresPrefunding:
          type: boolean
        status:
          type: string
        quoteExpiresAt:
          type: string
      required:
        - routeId
        - paymentId
        - destinationId
        - provider
        - sourceAmountBaseUnits
        - sourceAssetId
        - destinationAmountMinor
        - destinationCurrency
        - fees
        - exchangeRate
        - requiresBridge
        - requiresConversion
        - requiresPrefunding
        - status
        - quoteExpiresAt
    CorridorPaymentWithQuotesResponse:
      type: object
      properties:
        paymentId:
          type: string
        status:
          type: string
          enum:
            - CREATED
            - AWAITING_SOURCE_FUNDS
            - SOURCE_FUNDS_CONFIRMING
            - SOURCE_FUNDS_CONFIRMED
            - RECIPIENT_RESOLUTION
            - PENDING_CLAIM
            - PAYOUT_SELECTION_REQUIRED
            - POLICY_CHECK
            - COMPLIANCE_HOLD
            - ROUTE_QUOTED
            - AWAITING_RECIPIENT_CONFIRMATION
            - FUNDS_LOCKED
            - PROVIDER_SUBMITTED
            - PROVIDER_PROCESSING
            - ACTION_REQUIRED
            - DELIVERED
            - FAILED
            - REFUND_PENDING
            - REFUNDED
            - CANCELED
        recipientPhoneMasked:
          type: object
        recipientTenantId:
          type: object
        claimId:
          type: object
          description: Pending-claim id when the recipient is unresolved.
        sourceChainKey:
          type: string
        sourceAssetId:
          type: string
        sourceAmountBaseUnits:
          type: string
        sourceFundingMode:
          type: string
        requestedDestinationId:
          type: object
        selectedRouteQuoteId:
          type: object
        holdReasons:
          type: array
          items:
            type: string
        failureReason:
          type: object
        deliveredAt:
          type: object
        createdAt:
          type: string
        quotes:
          type: array
          items:
            $ref: '#/components/schemas/RouteQuoteResponse'
      required:
        - paymentId
        - status
        - sourceChainKey
        - sourceAssetId
        - sourceAmountBaseUnits
        - sourceFundingMode
        - holdReasons
        - createdAt
        - quotes
    CorridorPaymentResponse:
      type: object
      properties:
        paymentId:
          type: string
        status:
          type: string
          enum:
            - CREATED
            - AWAITING_SOURCE_FUNDS
            - SOURCE_FUNDS_CONFIRMING
            - SOURCE_FUNDS_CONFIRMED
            - RECIPIENT_RESOLUTION
            - PENDING_CLAIM
            - PAYOUT_SELECTION_REQUIRED
            - POLICY_CHECK
            - COMPLIANCE_HOLD
            - ROUTE_QUOTED
            - AWAITING_RECIPIENT_CONFIRMATION
            - FUNDS_LOCKED
            - PROVIDER_SUBMITTED
            - PROVIDER_PROCESSING
            - ACTION_REQUIRED
            - DELIVERED
            - FAILED
            - REFUND_PENDING
            - REFUNDED
            - CANCELED
        recipientPhoneMasked:
          type: object
        recipientTenantId:
          type: object
        claimId:
          type: object
          description: Pending-claim id when the recipient is unresolved.
        sourceChainKey:
          type: string
        sourceAssetId:
          type: string
        sourceAmountBaseUnits:
          type: string
        sourceFundingMode:
          type: string
        requestedDestinationId:
          type: object
        selectedRouteQuoteId:
          type: object
        holdReasons:
          type: array
          items:
            type: string
        failureReason:
          type: object
        deliveredAt:
          type: object
        createdAt:
          type: string
      required:
        - paymentId
        - status
        - sourceChainKey
        - sourceAssetId
        - sourceAmountBaseUnits
        - sourceFundingMode
        - holdReasons
        - createdAt
    CorridorPaymentListResponse:
      type: object
      properties:
        payments:
          type: array
          items:
            $ref: '#/components/schemas/CorridorPaymentResponse'
      required:
        - payments
    RouteQuoteListResponse:
      type: object
      properties:
        routes:
          type: array
          items:
            $ref: '#/components/schemas/RouteQuoteResponse'
      required:
        - routes
    SelectRouteBody:
      type: object
      properties:
        routeQuoteId:
          type: string
          description: Route quote id to select.
      required:
        - routeQuoteId
    ReviewNoteBody:
      type: object
      properties: {}
    ReviewInfoBody:
      type: object
      properties: {}
    ClaimPublicResponse:
      type: object
      properties:
        amountUsd:
          type: string
          example: '25.00'
          description: USD amount waiting in the claim.
        assetId:
          type: string
          description: Chain-scoped asset id.
        chainKey:
          type: string
          example: eip155:84532
        status:
          type: string
          description: Claim status (OPEN while claimable).
        expiresAt:
          type: string
        phoneMasked:
          type: object
          example: +1 ••• 4567
          description: Masked recipient phone.
      required:
        - amountUsd
        - assetId
        - chainKey
        - status
        - expiresAt
    VerifyPhoneResponse:
      type: object
      properties:
        challengeId:
          type: string
        expiresAt:
          type: string
      required:
        - challengeId
        - expiresAt
    ConfirmPhoneBody:
      type: object
      properties:
        challengeId:
          type: string
          description: Challenge id returned by verify-phone.
        code:
          type: string
          example: '123456'
          description: 6-digit code from the SMS.
      required:
        - challengeId
        - code
    ConfirmPhoneResponse:
      type: object
      properties:
        challengeId:
          type: string
        verified:
          type: boolean
      required:
        - challengeId
        - verified
    ClaimBody:
      type: object
      properties:
        challengeId:
          type: string
          description: A VERIFIED phone challenge id for this claim.
        destinationId:
          type: string
          description: One of the caller tenant's ACTIVE payout destination ids.
      required:
        - challengeId
        - destinationId
    ClaimResultResponse:
      type: object
      properties:
        paymentId:
          type: string
        status:
          type: string
        holdReasons:
          type: array
          items:
            type: string
        quotes:
          type: array
          items:
            $ref: '#/components/schemas/RouteQuoteResponse'
      required:
        - paymentId
        - status
        - holdReasons
        - quotes
    ResendSmsResponse:
      type: object
      properties:
        expiresAt:
          type: string
          description: Expiry of the freshly minted claim link.
      required:
        - expiresAt
    RefundClaimResponse:
      type: object
      properties:
        claimStatus:
          type: string
          description: Claim status after the refund request.
        paymentStatus:
          type: string
          description: Payment status after the refund request.
        refundTxHash:
          type: object
          description: Refund tx hash once the on-chain refund has been indexed.
      required:
        - claimStatus
        - paymentStatus
    DepositIntentDto:
      type: object
      properties: {}
    DepositIntentAmount:
      type: object
      properties:
        usd:
          type: string
          description: Human-readable USD amount (Decimal-backed string).
          example: '10.49'
        usdcBaseUnits:
          type: string
          description: USDC base units (1e-6) as a string. Use BigInt on the client.
          example: '10490000'
        decimals:
          type: number
          description: USDC decimal places (always 6).
          example: 6
      required:
        - usd
        - usdcBaseUnits
        - decimals
    DepositIntentResponse:
      type: object
      properties:
        txId:
          type: string
          description: Echo of the SafetapTransaction id from the request.
          example: 11111111-2222-3333-4444-555555555555
        tokenAddress:
          type: string
          description: USDC token contract on the active chain (Base mainnet or Sepolia).
          example: '0x036CbD53842c5426634e7929541eC2318f3dCF7e'
        escrowAddress:
          type: string
          description: SafeBank escrow contract to which the customer transfers USDC.
          example: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
        amount:
          $ref: '#/components/schemas/DepositIntentAmount'
        calldata:
          type: string
          description: >-
            Pre-encoded ERC-20 `transfer(escrowAddress, amount)` calldata.
            Client wallet can sign + broadcast without further encoding.
          example: 0xa9059cbb000000000000000000000000...
        chainId:
          type: number
          description: Chain id where the customer must broadcast the transfer.
          example: 84532
        expiresAt:
          type: object
          description: >-
            ISO 8601 expiry of the underlying SafetapTransaction. Null = no
            expiry.
          example: '2026-05-27T19:00:00.000Z'
          nullable: true
      required:
        - txId
        - tokenAddress
        - escrowAddress
        - amount
        - calldata
        - chainId
        - expiresAt
    ConfirmDepositDto:
      type: object
      properties:
        customerAddress:
          type: string
          description: >-
            Customer wallet whose escrow balance should be re-read. Sanctions
            screen runs against this address (mirrors POST
            /v1/safetap/deposit-intent).
          example: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1'
      required:
        - customerAddress
    ConfirmDepositResponse:
      type: object
      properties:
        cardholderId:
          type: string
          description: SafeBank cardholder UUID whose ledger was synced.
          example: 550e8400-e29b-41d4-a716-446655440000
        spendBalanceUsd:
          type: string
          description: >-
            Cardholder ledger spend balance after the re-read (USD,
            Decimal-backed).
          example: '125.75'
        escrowBaseUnits:
          type: string
          description: Raw on-chain escrow balance in USDC base units (1e-6).
          example: '125750000'
      required:
        - cardholderId
        - spendBalanceUsd
        - escrowBaseUnits
    StatementPeriodSummary:
      type: object
      properties:
        period:
          type: string
          description: Period (YYYY-MM).
          example: 2026-05
        frozen:
          type: boolean
          description: Whether a frozen snapshot exists for this period.
        transactionCount:
          type: number
          description: Transaction count in this period.
          example: 12
      required:
        - period
        - frozen
        - transactionCount
    StatementPeriodListResponse:
      type: object
      properties:
        periods:
          type: array
          items:
            $ref: '#/components/schemas/StatementPeriodSummary'
        total:
          type: number
          description: Total number of periods with activity.
          example: 3
      required:
        - periods
        - total
    StatementTransaction:
      type: object
      properties:
        id:
          type: string
          description: IssuingAuthorization UUID.
        amount:
          type: string
          description: Authorization amount (USD Decimal string).
          example: '49.99'
        currency:
          type: string
          example: USD
        mcc:
          type: object
          nullable: true
          description: Merchant category code, if reported.
        decision:
          type: string
          description: Authorization decision (APPROVED | DECLINED).
          example: APPROVED
        createdAt:
          type: string
          description: ISO 8601 authorization timestamp.
      required:
        - id
        - amount
        - currency
        - mcc
        - decision
        - createdAt
    StatementDispute:
      type: object
      properties:
        id:
          type: string
        reason:
          type: string
          enum:
            - UNAUTHORIZED
            - DUPLICATE
            - INCORRECT_AMOUNT
            - MERCHANDISE_NOT_RECEIVED
            - OTHER
        status:
          type: string
        amountDisputed:
          type: string
          description: USD disputed (Decimal string).
        createdAt:
          type: string
          description: ISO 8601 dispute creation timestamp.
      required:
        - id
        - reason
        - status
        - amountDisputed
        - createdAt
    StatementResponse:
      type: object
      properties:
        period:
          type: string
          description: Statement period (YYYY-MM).
          example: 2026-05
        cardholderId:
          type: string
          description: Cardholder UUID.
        frozen:
          type: boolean
          description: >-
            True when this statement is a frozen snapshot of a closed month
            (immutable). False for the current month (regenerated live each
            request, still accruing).
        transactions:
          type: array
          items:
            $ref: '#/components/schemas/StatementTransaction'
        disputes:
          type: array
          items:
            $ref: '#/components/schemas/StatementDispute'
        transactionCount:
          type: number
          description: Count of authorizations in the period.
          example: 12
        totalApprovedUsd:
          type: string
          description: >-
            Sum of APPROVED authorization amounts in the period (USD Decimal
            string).
          example: '342.18'
        declinedCount:
          type: number
          description: Count of DECLINED authorizations in the period.
          example: 1
        closingBalanceUsd:
          type: string
          description: >-
            Spend balance at the close of the period (USD). For the current
            (live) month this is the current balance; for a frozen month it is
            the balance captured at freeze time.
          example: '125.75'
        generatedAt:
          type: string
          description: ISO 8601 timestamp the statement was generated/frozen.
        disclosureRef:
          type: string
          description: >-
            Reg E disclosure reference — fetch full text at
            /v1/consumer/reg-e-disclosure.
          example: /v1/consumer/reg-e-disclosure
      required:
        - period
        - cardholderId
        - frozen
        - transactions
        - disputes
        - transactionCount
        - totalApprovedUsd
        - declinedCount
        - closingBalanceUsd
        - generatedAt
        - disclosureRef
    ReceiptResponse:
      type: object
      properties:
        id:
          type: string
          description: SettlementSplit UUID (== receipt id).
        txHash:
          type: string
          description: On-chain settlement transaction hash.
          example: 0xa1b2c3...
        merchantName:
          type: object
          description: Merchant display name. Null if denormalised relation missing.
          nullable: true
        merchantId:
          type: object
          nullable: true
        partnerId:
          type: object
          nullable: true
        grossUsdc:
          type: string
          description: Gross USDC settled (base units, 1e-6 string).
        merchantUsdc:
          type: string
          description: Merchant slice of the gross (base units string).
        safebankUsdc:
          type: string
          description: SafeBank fee slice (base units string).
        partnerUsdc:
          type: string
          description: Partner fee slice (base units string).
        feeBps:
          type: number
          description: Total fee in basis points.
          example: 250
        partnerRevShareBps:
          type: number
          description: Partner rev-share of the fee in basis points.
          example: 50
        stripeAuthId:
          type: object
          nullable: true
        safetapTxId:
          type: object
          nullable: true
        createdAt:
          type: string
          description: ISO 8601 settlement timestamp.
        chainId:
          type: number
          description: Chain id the settlement occurred on. Drives BaseScan link rendering.
          example: 84532
      required:
        - id
        - txHash
        - merchantName
        - merchantId
        - partnerId
        - grossUsdc
        - merchantUsdc
        - safebankUsdc
        - partnerUsdc
        - feeBps
        - partnerRevShareBps
        - stripeAuthId
        - safetapTxId
        - createdAt
        - chainId
    AttestationDomain:
      type: object
      properties:
        name:
          type: string
          example: SafeBank
        version:
          type: string
          example: '1'
        chainId:
          type: number
          example: 84532
        verifyingContract:
          type: string
          example: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
      required:
        - name
        - version
        - chainId
        - verifyingContract
    AttestationMessage:
      type: object
      properties:
        paymentRequestId:
          type: string
          example: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
        grossUsdc:
          type: string
          description: Gross USDC (base units string).
        merchantNetUsdc:
          type: string
          description: Merchant slice (base units string).
        safebankFeeUsdc:
          type: string
          description: SafeBank fee (base units string).
        partnerFeeUsdc:
          type: string
          description: Partner fee (base units string).
        nonce:
          type: string
          description: Anti-replay nonce.
        expiresAt:
          type: string
          description: ISO 8601 expiry (informational; signature itself does not expire).
      required:
        - paymentRequestId
        - grossUsdc
        - merchantNetUsdc
        - safebankFeeUsdc
        - partnerFeeUsdc
        - nonce
        - expiresAt
    ReceiptAttestationResponse:
      type: object
      properties:
        settlementSplitId:
          type: string
        txHash:
          type: string
        domain:
          $ref: '#/components/schemas/AttestationDomain'
        message:
          $ref: '#/components/schemas/AttestationMessage'
        signature:
          type: string
          description: EIP-712 signature (65-byte hex).
          example: >-
            0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
        signerAddress:
          type: string
          description: >-
            Recovered signer address. Verifiers should pin this for the SafeBank
            deploy.
          example: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
      required:
        - settlementSplitId
        - txHash
        - domain
        - message
        - signature
        - signerAddress
    RefundBody:
      type: object
      properties:
        settlementSplitId:
          type: string
          description: SettlementSplit row id targeting the original payment.
          example: a1b2c3d4-5678-90ab-cdef-1234567890ab
        customerWalletAddress:
          type: string
          description: Customer Ethereum address that receives the refunded USDC.
          example: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1'
        amountUsdcBaseUnits:
          type: string
          description: >-
            Base-units (1e-6 USDC) integer string. Defaults to the merchantUsdc
            slice of the settlement. Must be ≤ remaining refundable amount;
            partial refunds compose.
          example: '41580000'
        reason:
          type: string
          description: >-
            Optional free-text reason. Logged for audit; not surfaced to the
            customer.
          example: customer requested partial refund
      required:
        - settlementSplitId
        - customerWalletAddress
    RefundResponse:
      type: object
      properties:
        partnerId:
          type: string
          description: >-
            Echo of the partnerId from the URL — useful for client-side
            correlation.
          example: partner_abc123
        refundId:
          type: string
          description: >-
            New refund event UUID; use to poll status or correlate the on-chain
            tx.
          example: b2e1f3a0-8c34-4d2a-9d12-bd55b15f02b1
        status:
          type: string
          description: >-
            Lifecycle status. PENDING → SUBMITTED → CONFIRMED, or terminal
            FAILED.
          enum:
            - PENDING
            - SUBMITTED
            - CONFIRMED
            - FAILED
          example: PENDING
        txHash:
          type: object
          description: >-
            On-chain refund transaction hash. Null until status reaches
            SUBMITTED.
          example: 0xa1b2c3...
          nullable: true
      required:
        - partnerId
        - refundId
        - status
    MetaResponse:
      type: object
      properties:
        name:
          type: string
          example: SafeBank Infrastructure API
        version:
          type: string
          example: 1.2.3
          description: >-
            Build-time semver from API_VERSION env. Falls back to "0.0.0-dev"
            when unset.
        commit:
          type: string
          example: abc1234
          description: Short git SHA injected by the deploy pipeline. "unknown" when unset.
        env:
          type: string
          example: production
          enum:
            - development
            - test
            - staging
            - production
        time:
          type: string
          example: '2026-05-27T18:00:00.000Z'
          description: Server-side wall clock at response time (ISO 8601 UTC).
        requestId:
          type: string
          example: 11111111-2222-3333-4444-555555555555
          description: >-
            Per-request UUID generated (or echoed) by the X-Request-Id
            middleware. Quoting this in support tickets short-circuits log
            triage.
      required:
        - name
        - version
        - commit
        - env
        - time
        - requestId
    OwnerInputDto:
      type: object
      properties:
        address:
          type: string
          example: '0xa11ce0000000000000000000000000000000aaaa'
        role:
          type: string
          enum:
            - ADMIN_OWNER
            - FINANCE_OWNER
            - MERCHANT_OPERATOR
            - TERMINAL_PHONE
            - VIEWER
        label:
          type: string
          maxLength: 80
        isCreator:
          type: boolean
      required:
        - address
        - role
    CreateMultisigWalletDto:
      type: object
      properties:
        name:
          type: string
          maxLength: 80
        purpose:
          type: string
          enum:
            - MERCHANT
            - DAO
            - CONSUMER
        merchantId:
          type: string
        daoId:
          type: string
        consumerPrivyUserId:
          type: string
        chainFamily:
          type: string
          enum:
            - EVM
            - SOLANA
        chainId:
          type: number
          description: Required when chainFamily=EVM
        solanaCluster:
          type: string
          description: Required when chainFamily=SOLANA
        threshold:
          type: number
          minimum: 1
        owners:
          type: array
          items:
            $ref: '#/components/schemas/OwnerInputDto'
      required:
        - name
        - purpose
        - chainFamily
        - threshold
        - owners
    CreateMultisigGroupDeploymentDto:
      type: object
      properties:
        chainFamily:
          type: string
          enum:
            - EVM
            - SOLANA
        chainId:
          type: number
        cluster:
          type: string
        threshold:
          type: number
          minimum: 1
        owners:
          type: array
          items:
            $ref: '#/components/schemas/OwnerInputDto'
      required:
        - chainFamily
        - threshold
        - owners
    CreateMultisigGroupDto:
      type: object
      properties:
        name:
          type: string
        purpose:
          type: string
          enum:
            - MERCHANT
            - DAO
            - CONSUMER
        merchantId:
          type: string
        daoId:
          type: string
        consumerPrivyUserId:
          type: string
        deployments:
          type: array
          items:
            $ref: '#/components/schemas/CreateMultisigGroupDeploymentDto'
      required:
        - name
        - purpose
        - deployments
    ProposeTransactionDto:
      type: object
      properties:
        to:
          type: string
        value:
          type: string
          description: wei amount as a base-10 integer string
        data:
          type: string
          description: 0x-prefixed hex calldata
        operation:
          type: string
          enum:
            - CALL
            - DELEGATECALL
        description:
          type: string
          maxLength: 240
      required:
        - to
        - value
        - data
    SignTransactionDto:
      type: object
      properties:
        signature:
          type: string
      required:
        - signature
    ChainCapability:
      type: object
      properties:
        chainId:
          type: number
          example: 84532
          description: EVM chain id.
        name:
          type: string
          example: Base Sepolia
        family:
          type: string
          example: EVM
          enum:
            - EVM
        testnet:
          type: boolean
          example: true
          description: True for testnets (e.g. Base Sepolia).
        usdcAddress:
          type: string
          example: '0x036CbD53842c5426634e7929541eC2318f3dCF7e'
          description: Canonical USDC token address on this chain, when known.
      required:
        - chainId
        - name
        - family
        - testnet
    IdentityCapability:
      type: object
      properties:
        sessions:
          type: boolean
          example: true
          description: KYC/KYB verification sessions + credentials.
        provider:
          type: string
          example: mock
          description: Active identity provider key (mock = sandbox).
        live:
          type: boolean
          example: false
          description: >-
            True only when a real vendor adapter is configured; mock is
            sandbox-only.
        enforcement:
          type: string
          enum:
            - 'off'
            - log
            - enforce
          description: Payment-path credential gating rollout stage.
      required:
        - sessions
        - provider
        - live
        - enforcement
    CardsCapability:
      type: object
      properties:
        issuing:
          type: boolean
          example: true
          description: >-
            Card issuing is live when a Stripe or Bridge issuing backend is
            configured.
        freeze:
          type: boolean
          example: true
          description: Card freeze/unfreeze via POST /v1/cards/:id/freeze|unfreeze.
        limits:
          type: boolean
          example: true
          description: >-
            Per-card daily/weekly/monthly + MCC spend limits via PUT
            /v1/cards/:id/limits.
      required:
        - issuing
        - freeze
        - limits
    FeaturesCapability:
      type: object
      properties:
        identity:
          $ref: '#/components/schemas/IdentityCapability'
        wallets:
          type: boolean
          example: true
          description: Multisig (Safe) wallet provisioning + management.
        payments:
          type: boolean
          example: true
          description: On-chain USDC payments via the multisig rails.
        cards:
          $ref: '#/components/schemas/CardsCapability'
        intents:
          type: boolean
          example: false
          description: Composable payment intents — not shipped in this branch.
        simulate:
          type: boolean
          example: false
          description: Dry-run transaction simulation — not shipped in this branch.
        autoramps:
          type: boolean
          example: false
          description: Fiat <-> stablecoin auto on/off-ramps — not shipped in this branch.
        treasury:
          type: boolean
          example: false
          description: Treasury / yield features — not shipped in this branch.
        agents:
          type: boolean
          example: true
          description: >-
            Agent surface: identity, scoped keys, dedicated wallet + card,
            activity audit, and hard synchronous spend enforcement on the card
            rail (atomic per-agent consume on every authorization). True in
            sandbox; false in production until live agent card issuance ships.
            Payment-rail agent caps remain best-effort.
        invoices:
          type: boolean
          example: true
          description: Invoices with per-invoice 2-of-3 escrow hold Safes (sandbox).
        offramp:
          type: boolean
          example: true
          description: >-
            Off-ramp corridors: phone-addressed payments, route quotes with
            recipient-only consent, and the settlement saga (sandbox/mock
            provider rails; live refused).
        claims:
          type: boolean
          example: true
          description: >-
            Pending claims with the SMS claim flow for unknown recipients
            (sandbox).
        destinationRails:
          type: boolean
          example: true
          description: >-
            Rail-typed payout destinations
            (PayPal/Venmo/ACH/SEPA/on-chain/balance).
      required:
        - identity
        - wallets
        - payments
        - cards
        - intents
        - simulate
        - autoramps
        - treasury
        - agents
        - invoices
        - offramp
        - claims
        - destinationRails
    CapabilitiesResponse:
      type: object
      properties:
        apiVersion:
          type: string
          example: '1'
        environment:
          type: string
          example: sandbox
          enum:
            - sandbox
            - production
          description: >-
            Deployment environment this API instance runs as (from NODE_ENV;
            defaults to sandbox).
        chains:
          description: >-
            EVM chains the platform is configured for, derived from
            MULTISIG_EVM_CHAIN_IDS.
          type: array
          items:
            $ref: '#/components/schemas/ChainCapability'
        features:
          $ref: '#/components/schemas/FeaturesCapability'
      required:
        - apiVersion
        - environment
        - chains
        - features
    CreateTenantBody:
      type: object
      properties:
        name:
          type: string
          description: Human-readable tenant name. Defaults to a generated name.
          example: Acme Robotics
        slug:
          type: string
          description: >-
            URL-safe slug (lowercase letters, digits, hyphens). Auto-generated
            from the name when omitted; a numeric suffix is appended if the slug
            is taken.
          example: acme-robotics
    TenantResponse:
      type: object
      properties:
        id:
          type: string
          example: b3f1c2a0-1111-2222-3333-444455556666
        slug:
          type: string
          example: acme-robotics
        name:
          type: string
          example: Acme Robotics
        role:
          type: string
          description: The caller's role in this tenant.
          example: OWNER
        createdAt:
          type: string
          example: '2026-08-09T12:00:00.000Z'
      required:
        - id
        - slug
        - name
        - role
        - createdAt
    InviteMemberBody:
      type: object
      properties:
        email:
          type: string
          description: Invitee email.
          example: bob@acme.com
        displayName:
          type: string
          description: Display name for the member.
          example: Bob Jones
        role:
          type: string
          enum:
            - ADMIN
            - MEMBER
          default: MEMBER
      required:
        - email
    InviteMemberResponse:
      type: object
      properties:
        id:
          type: string
        email:
          type: object
        displayName:
          type: object
        role:
          type: string
          example: MEMBER
        status:
          type: string
          enum:
            - PENDING
            - ACTIVE
            - REVOKED
        signerAddress:
          type: object
          description: The member’s on-chain signer address (null until accepted).
          example: '0x83172d9A819b7895cEE10F4B52bB4Ec40114D39C'
        privyUserId:
          type: object
          description: Linked Privy identity (null for CLI-accepted members).
        createdAt:
          type: string
        acceptedAt:
          type: object
        inviteToken:
          type: string
          description: >-
            One-time invite token — share it with the invitee. Not stored or
            shown again.
      required:
        - id
        - role
        - status
        - createdAt
        - inviteToken
    MemberResponse:
      type: object
      properties:
        id:
          type: string
        email:
          type: object
        displayName:
          type: object
        role:
          type: string
          example: MEMBER
        status:
          type: string
          enum:
            - PENDING
            - ACTIVE
            - REVOKED
        signerAddress:
          type: object
          description: The member’s on-chain signer address (null until accepted).
          example: '0x83172d9A819b7895cEE10F4B52bB4Ec40114D39C'
        privyUserId:
          type: object
          description: Linked Privy identity (null for CLI-accepted members).
        createdAt:
          type: string
        acceptedAt:
          type: object
      required:
        - id
        - role
        - status
        - createdAt
    MemberListResponse:
      type: object
      properties:
        members:
          type: array
          items:
            $ref: '#/components/schemas/MemberResponse'
      required:
        - members
    RemoveMemberResponse:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
          example: REVOKED
        stillOwnerOfWalletIds:
          description: >-
            Wallets where this member is still an active on-chain Safe owner.
            Revoking membership does NOT remove Safe ownership — remove them
            from each wallet with an owner-admin tx.
          example: []
          type: array
          items:
            type: string
      required:
        - id
        - status
        - stillOwnerOfWalletIds
    AcceptMemberBody:
      type: object
      properties:
        token:
          type: string
          description: The one-time invite token from the invite response.
        signerAddress:
          type: string
          description: >-
            The EVM address the member will sign with (their CLI signer or
            wallet address). Becomes the on-chain owner key when they are added
            to a Safe.
          example: '0x83172d9A819b7895cEE10F4B52bB4Ec40114D39C'
        displayName:
          type: string
          description: Optional display name override.
      required:
        - token
        - signerAddress
    CreateApiKeyBody:
      type: object
      properties:
        name:
          type: string
          description: Human label for the key.
          example: CI sandbox
        environment:
          type: string
          description: Environment. Defaults to `test`.
          enum:
            - test
            - live
          example: test
        scopes:
          type: array
          description: >-
            Granted permission scopes. Defaults to a read-only set. Use `*` for
            a root key. Agent keys (created via the agents API) cannot hold
            agents:manage / keys:manage / policies:write.
          example:
            - wallets:write
            - wallets:fund
            - payments:create
          items:
            type: string
            enum:
              - wallets:read
              - wallets:write
              - wallets:fund
              - payments:read
              - payments:create
              - payments:execute
              - cards:read
              - cards:issue
              - cards:manage
              - policies:read
              - policies:write
              - policies:evaluate
              - agents:read
              - agents:manage
              - treasury:read
              - treasury:write
              - invoices:read
              - invoices:write
              - invoices:release
              - invoices:pay
              - ramps:read
              - ramps:write
              - keys:manage
              - members:read
              - members:manage
              - contacts:read
              - contacts:write
              - identity:read
              - identity:write
              - status:read
              - twofactor:read
              - twofactor:manage
              - '*'
        expiresAt:
          type: string
          description: Optional ISO-8601 expiry. A key past its expiry fails auth.
          example: '2027-01-01T00:00:00.000Z'
      required:
        - name
    CreatedApiKeyResponse:
      type: object
      properties:
        id:
          type: string
          example: c1a2b3d4-...
        key:
          type: string
          description: >-
            The full plaintext key — shown ONCE. Store it now; it is not
            recoverable.
          example: sb_test_Ab12Cd34Ef56_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
        pubId:
          type: string
          example: Ab12Cd34Ef56
          description: Public id segment (safe to log/display).
        environment:
          type: string
          enum:
            - TEST
            - LIVE
          example: TEST
        scopes:
          example:
            - wallets:write
            - payments:create
          type: array
          items:
            type: array
        expiresAt:
          type: object
          nullable: true
          example: null
        createdAt:
          type: string
          example: '2026-08-09T12:00:00.000Z'
      required:
        - id
        - key
        - pubId
        - environment
        - scopes
        - expiresAt
        - createdAt
    ApiKeyResponse:
      type: object
      properties:
        id:
          type: string
          example: c1a2b3d4-...
        name:
          type: string
          example: CI sandbox
        pubId:
          type: string
          example: Ab12Cd34Ef56
        environment:
          type: string
          enum:
            - TEST
            - LIVE
          example: TEST
        scopes:
          example:
            - wallets:write
          type: array
          items:
            type: array
        lastUsedAt:
          type: object
          nullable: true
          example: '2026-08-09T12:30:00.000Z'
        expiresAt:
          type: object
          nullable: true
          example: null
        revokedAt:
          type: object
          nullable: true
          example: null
        createdAt:
          type: string
          example: '2026-08-09T12:00:00.000Z'
      required:
        - id
        - name
        - pubId
        - environment
        - scopes
        - lastUsedAt
        - expiresAt
        - revokedAt
        - createdAt
    ApiKeyListResponse:
      type: object
      properties:
        keys:
          type: array
          items:
            $ref: '#/components/schemas/ApiKeyResponse'
      required:
        - keys
    CreateAgentBody:
      type: object
      properties:
        name:
          type: string
          description: Human label for the agent.
          example: ap-bot
      required:
        - name
    AgentResponse:
      type: object
      properties:
        id:
          type: string
          example: a1b2c3d4-5e6f-7890-abcd-ef1234567890
        name:
          type: string
          example: ap-bot
        status:
          type: string
          enum:
            - ACTIVE
            - SUSPENDED
          example: ACTIVE
        createdAt:
          type: string
          example: '2026-08-10T00:00:00.000Z'
      required:
        - id
        - name
        - status
        - createdAt
    AgentListResponse:
      type: object
      properties:
        agents:
          type: array
          items:
            $ref: '#/components/schemas/AgentResponse'
      required:
        - agents
    AgentLimitsResponse:
      type: object
      properties:
        agentId:
          type: string
          example: a1b2c3d4-5e6f-7890-abcd-ef1234567890
        perTransactionUsd:
          type: object
          description: Per-payment USD cap, or null.
          example: '50.00'
          nullable: true
        dailyUsd:
          type: object
          description: Rolling 24h USD cap, or null.
          example: '500.00'
          nullable: true
        weeklyUsd:
          type: object
          description: Rolling ISO-week USD cap, or null.
          example: '2000.00'
          nullable: true
        monthlyUsd:
          type: object
          description: Calendar-month USD cap, or null.
          example: '5000.00'
          nullable: true
      required:
        - agentId
        - perTransactionUsd
        - dailyUsd
        - weeklyUsd
        - monthlyUsd
    SetAgentLimitsBody:
      type: object
      properties:
        perTransactionUsd:
          type: object
          description: Per-payment USD cap. `null` clears it.
          example: '50.00'
          nullable: true
        dailyUsd:
          type: object
          description: Rolling 24h USD cap. `null` clears it.
          example: '500.00'
          nullable: true
        weeklyUsd:
          type: object
          description: Rolling ISO-week USD cap. `null` clears it.
          example: '2000.00'
          nullable: true
        monthlyUsd:
          type: object
          description: Calendar-month USD cap. `null` clears it.
          example: '5000.00'
          nullable: true
    CreateAgentKeyBody:
      type: object
      properties:
        name:
          type: string
          description: Human label for the key.
          example: ap-bot ci
        environment:
          type: string
          description: Environment. Defaults to `test`.
          enum:
            - test
            - live
          example: test
        scopes:
          type: array
          description: >-
            Granted scopes. Clamped to the agent ceiling — an agent key can
            never hold `*`, agents:manage, keys:manage, or policies:write.
            Defaults to wallets:read + payments:read/create.
          example:
            - wallets:read
            - payments:create
          items:
            type: string
            enum:
              - wallets:read
              - wallets:write
              - wallets:fund
              - payments:read
              - payments:create
              - payments:execute
              - cards:read
              - cards:issue
              - cards:manage
              - policies:read
              - policies:evaluate
              - agents:read
              - treasury:read
              - invoices:read
              - invoices:write
              - invoices:release
              - invoices:pay
              - ramps:read
              - ramps:write
              - members:read
              - contacts:read
              - contacts:write
              - identity:read
              - status:read
              - twofactor:read
        expiresAt:
          type: string
          description: Optional ISO-8601 expiry. A key past its expiry fails auth.
          example: '2027-01-01T00:00:00.000Z'
      required:
        - name
    WalletOwnerInput:
      type: object
      properties:
        address:
          type: string
          description: >-
            EVM owner address (EIP-55). Provide exactly one of address /
            memberId / handle.
          example: 0x51c2…88
        memberId:
          type: string
          description: A tenant member id — resolves to their signer address.
        handle:
          type: string
          description: A global @handle — resolves to its address.
        role:
          type: string
          description: On-chain owner role. Defaults to ADMIN_OWNER.
          enum:
            - ADMIN_OWNER
            - FINANCE_OWNER
            - MERCHANT_OPERATOR
            - AGENT
        label:
          type: string
          description: Optional label for the owner.
    ProvisionAgentWalletBody:
      type: object
      properties:
        owners:
          description: Safe owners (tenant signers).
          type: array
          items:
            $ref: '#/components/schemas/WalletOwnerInput'
        threshold:
          type: number
          description: Signature threshold. Defaults to 1.
          example: 1
        chainId:
          type: number
          description: EVM chain id. Defaults to the sandbox chain.
          example: 84532
      required:
        - owners
    AgentWalletResponse:
      type: object
      properties:
        agentId:
          type: string
          example: a1b2c3d4-5e6f-7890-abcd-ef1234567890
        walletId:
          type: string
          description: MultisigWallet id of the dedicated Safe.
        address:
          type: object
          description: Predicted CREATE2 address.
          nullable: true
        status:
          type: string
          description: Deployment status.
          example: PENDING_DEPLOYMENT
        chainId:
          type: number
          example: 84532
        threshold:
          type: number
          example: 1
      required:
        - agentId
        - walletId
        - address
        - status
        - chainId
        - threshold
    IssueAgentCardBody:
      type: object
      properties:
        perTransactionUsd:
          type: object
          description: Per-transaction USD cap. `null` clears it.
          example: '250.00'
          nullable: true
        dailyUsd:
          type: object
          description: Rolling 24h USD cap. `null` clears it.
          example: '500.00'
          nullable: true
        weeklyUsd:
          type: object
          description: Rolling ISO-week USD cap. `null` clears it.
          example: '2000.00'
          nullable: true
        monthlyUsd:
          type: object
          description: Calendar-month USD cap. `null` clears it.
          example: '5000.00'
          nullable: true
        blockedMcc:
          type: array
          description: MCCs to decline. Replaces the existing list.
          items:
            type: string
          example:
            - '7995'
        allowedMcc:
          type: array
          description: >-
            When set, only these MCCs are allowed. `null` clears the allowlist
            (allow anything not blocked).
          items:
            type: string
          example:
            - '5411'
            - '5812'
          nullable: true
    AgentActivityItemDto:
      type: object
      properties:
        type:
          type: string
          enum:
            - payment
            - card_authorization
            - policy_decision
        id:
          type: string
        at:
          type: string
          description: ISO-8601 timestamp.
      required:
        - type
        - id
        - at
    AgentActivityResponseDto:
      type: object
      properties:
        agentId:
          type: string
        items:
          description: >-
            Newest-first merge of the agent’s payments, card authorizations, and
            policy decisions. Items carry type-specific fields
            (status/amountUsd/destination for payments;
            decision/declineReason/mcc/cardLast4 for authorizations;
            action/decision/reason/policyId/sha256 for policy decisions).
          type: array
          items:
            $ref: '#/components/schemas/AgentActivityItemDto'
      required:
        - agentId
        - items
    CreateWalletBody:
      type: object
      properties:
        name:
          type: string
          description: Human name for the wallet.
          example: Acme Treasury
        owners:
          description: On-chain owners. At least one; the first defaults to ADMIN_OWNER.
          type: array
          items:
            $ref: '#/components/schemas/WalletOwnerInput'
        threshold:
          type: number
          description: Signature threshold. Defaults to 1 (sandbox single-owner).
          example: 1
        chainId:
          type: number
          description: EVM chain id. Defaults to the platform default (Base Sepolia 84532).
          example: 84532
      required:
        - name
        - owners
    WalletOwnerView:
      type: object
      properties:
        address:
          type: string
        role:
          type: string
        label:
          type: object
          nullable: true
      required:
        - address
        - role
        - label
    WalletBalance:
      type: object
      properties:
        asset:
          type: string
          example: usdc
        amount:
          type: string
          description: Human decimal amount.
          example: '250.00'
        decimals:
          type: number
          example: 6
      required:
        - asset
        - amount
        - decimals
    WalletResponse:
      type: object
      properties:
        id:
          type: string
        address:
          type: object
          nullable: true
          description: CREATE2 address (usable before deploy).
        status:
          type: string
          enum:
            - PENDING_DEPLOYMENT
            - DEPLOYING
            - DEPLOYED
            - FAILED
        chainId:
          type: object
          nullable: true
        threshold:
          type: number
        predictedAddress:
          type: boolean
          description: True until the Safe proxy is mined.
        name:
          type: string
        owners:
          type: array
          items:
            $ref: '#/components/schemas/WalletOwnerView'
        balances:
          description: Live on-chain balances (present on detail reads; omitted from list).
          type: array
          items:
            $ref: '#/components/schemas/WalletBalance'
        createdAt:
          type: string
      required:
        - id
        - address
        - status
        - chainId
        - threshold
        - predictedAddress
        - name
        - owners
        - createdAt
    WalletListResponse:
      type: object
      properties:
        wallets:
          type: array
          items:
            $ref: '#/components/schemas/WalletResponse'
        total:
          type: number
        skip:
          type: number
        take:
          type: number
      required:
        - wallets
        - total
        - skip
        - take
    PendingTxResponse:
      type: object
      properties:
        txId:
          type: string
        walletId:
          type: string
        walletName:
          type: string
        to:
          type: string
        value:
          type: string
        safeTxHash:
          type: object
        status:
          type: string
          enum:
            - PROPOSED
            - SIGNING
            - READY_TO_EXECUTE
        signatureCount:
          type: number
        threshold:
          type: number
        createdAt:
          type: string
      required:
        - txId
        - walletId
        - walletName
        - to
        - value
        - status
        - signatureCount
        - threshold
        - createdAt
    PendingTxListResponse:
      type: object
      properties:
        pending:
          type: array
          items:
            $ref: '#/components/schemas/PendingTxResponse'
      required:
        - pending
    TransactionDetailResponse:
      type: object
      properties:
        id:
          type: string
        walletId:
          type: string
        to:
          type: string
        value:
          type: string
        data:
          type: string
        safeTxHash:
          type: object
        status:
          type: string
        signers:
          description: Owner addresses that have signed.
          type: array
          items:
            type: string
        signatureCount:
          type: number
        threshold:
          type: number
        createdAt:
          type: string
      required:
        - id
        - walletId
        - to
        - value
        - data
        - status
        - signers
        - signatureCount
        - threshold
        - createdAt
    ProposeTransactionBody:
      type: object
      properties:
        to:
          type: string
          description: Destination contract/EOA (EIP-55).
          example: 0x036CbD…
        value:
          type: string
          description: Native value in wei (decimal string). Defaults to "0".
          example: '0'
        data:
          type: string
          description: Calldata hex. Defaults to 0x.
          example: 0xa9059cbb…
        ownerAddress:
          type: string
          description: >-
            Owner address to act as (must be a registered on-chain owner).
            Optional when the wallet has exactly one on-chain owner.
      required:
        - to
    SignTransactionBody:
      type: object
      properties:
        signature:
          type: string
          description: >-
            Raw secp256k1 signature over the 32-byte safeTxHash (NOT
            personal_sign).
          example: 0x…
      required:
        - signature
    ExecuteTransactionBody:
      type: object
      properties:
        ownerAddress:
          type: string
          description: Owner address to act as. Optional for single-owner wallets.
    AddOwnerBody:
      type: object
      properties:
        newOwner:
          type: string
          description: New owner address (EIP-55).
        threshold:
          type: number
          description: Resulting signature threshold (1..ownerCount+1).
          example: 2
        role:
          type: string
          description: DB role for the new owner (default FINANCE_OWNER).
          enum:
            - ADMIN_OWNER
            - FINANCE_OWNER
            - MERCHANT_OPERATOR
        ownerAddress:
          type: string
          description: Acting owner (ADMIN_OWNER). Optional if sole owner.
      required:
        - newOwner
        - threshold
    RemoveOwnerBody:
      type: object
      properties:
        threshold:
          type: number
          description: Resulting signature threshold (1..ownerCount-1).
          example: 1
        ownerAddress:
          type: string
          description: Acting owner (ADMIN_OWNER). Optional if sole owner.
      required:
        - threshold
    SwapOwnerBody:
      type: object
      properties:
        newOwner:
          type: string
          description: The replacement owner address (EIP-55).
        ownerAddress:
          type: string
          description: Acting owner (ADMIN_OWNER). Optional if sole owner.
      required:
        - newOwner
    ChangeThresholdBody:
      type: object
      properties:
        threshold:
          type: number
          description: New signature threshold (1..ownerCount).
          example: 2
        ownerAddress:
          type: string
          description: Acting owner (ADMIN_OWNER). Optional if sole owner.
      required:
        - threshold
    FundWalletBody:
      type: object
      properties:
        asset:
          type: string
          description: Asset to drip. Defaults to `usdc`.
          enum:
            - usdc
            - eth
          example: usdc
        amount:
          type: string
          description: >-
            Human decimal amount (e.g. "250" USDC, "0.01" ETH). Defaults to a
            per-asset default. Capped at 1000 USDC / 0.02 ETH per drip.
          example: '250'
    DripResponse:
      type: object
      properties:
        id:
          type: string
          example: d1a2b3c4-...
        walletId:
          type: string
          example: w1
        asset:
          type: string
          enum:
            - usdc
            - eth
          example: usdc
        amount:
          type: string
          description: Human decimal amount dripped.
          example: '250'
        chainId:
          type: number
          example: 84532
        status:
          type: string
          enum:
            - SUBMITTED
            - CONFIRMED
            - FAILED
        txHash:
          type: object
          nullable: true
          description: On-chain tx hash once submitted.
        failureReason:
          type: object
          nullable: true
        createdAt:
          type: string
          example: '2026-08-09T12:00:00.000Z'
      required:
        - id
        - walletId
        - asset
        - amount
        - chainId
        - status
        - txHash
        - failureReason
        - createdAt
    DripListResponse:
      type: object
      properties:
        drips:
          type: array
          items:
            $ref: '#/components/schemas/DripResponse'
      required:
        - drips
    HandleResponse:
      type: object
      properties:
        handle:
          type: string
          example: acme-treasury
        displayName:
          type: object
          nullable: true
        address:
          type: string
          example: 0x7Fb2…c41A
        chainId:
          type: number
          example: 84532
        verified:
          type: boolean
          example: true
      required:
        - handle
        - displayName
        - address
        - chainId
        - verified
    ClaimHandleBody:
      type: object
      properties:
        handle:
          type: string
          description: Handle to claim (without the @).
          example: acme-treasury
        walletId:
          type: string
          description: Wallet id this handle resolves to.
          example: w1
        discoverableByEmail:
          type: boolean
          description: Opt in to email-based resolution.
          example: false
        email:
          type: string
          description: Contact email for email resolution.
      required:
        - handle
        - walletId
    ContactDestinationInput:
      type: object
      properties:
        type:
          type: string
          enum:
            - handle
            - address
            - email
            - wallet
        handle:
          type: string
          description: Required when type=handle.
        address:
          type: string
          description: Required when type=address (EIP-55).
        email:
          type: string
          description: Required when type=email.
        walletId:
          type: string
          description: Required when type=wallet (one of your wallets).
        chainId:
          type: number
          description: Optional chain id for type=address.
      required:
        - type
    CreateContactBody:
      type: object
      properties:
        label:
          type: string
          description: Short label used as `@label` at pay time.
          example: acme
        displayName:
          type: string
          example: Acme Robotics
        notes:
          type: string
        destination:
          $ref: '#/components/schemas/ContactDestinationInput'
      required:
        - label
        - destination
    ContactResponse:
      type: object
      properties:
        id:
          type: string
        label:
          type: string
        displayName:
          type: object
        notes:
          type: object
        destination:
          $ref: '#/components/schemas/ContactDestinationInput'
        createdAt:
          type: string
      required:
        - id
        - label
        - destination
        - createdAt
    ContactListResponse:
      type: object
      properties:
        contacts:
          type: array
          items:
            $ref: '#/components/schemas/ContactResponse'
      required:
        - contacts
    UpdateContactBody:
      type: object
      properties:
        displayName:
          type: string
        notes:
          type: string
        destination:
          $ref: '#/components/schemas/ContactDestinationInput'
    CreateVerificationSessionBody:
      type: object
      properties:
        kind:
          type: string
          enum:
            - KYC
            - KYB
          description: KYC verifies a member; KYB the tenant.
        memberId:
          type: string
          description: TenantMember id — required for KYC, ignored for KYB.
        legalName:
          type: string
          description: >-
            Subject legal name. Defaults to the member display name (KYC) or
            tenant name (KYB).
        email:
          type: string
          description: Subject contact email; defaults to the member email.
        country:
          type: string
          description: ISO-3166 alpha-2 country code.
          example: DE
      required:
        - kind
    VerificationRequirementResponse:
      type: object
      properties:
        type:
          type: string
          example: authorized_representative_kyc
        status:
          type: string
          enum:
            - PENDING
            - ACTION_REQUIRED
            - PROCESSING
            - COMPLETED
            - FAILED
        subjectRef:
          type: string
      required:
        - type
        - status
    VerificationSessionResponse:
      type: object
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - KYC
            - KYB
        subjectType:
          type: string
          enum:
            - TENANT
            - TENANT_MEMBER
        subjectId:
          type: string
        legalName:
          type: string
        status:
          type: string
          enum:
            - CREATED
            - ACTION_REQUIRED
            - IN_PROGRESS
            - PROCESSING
            - PENDING_REVIEW
            - ADDITIONAL_INFORMATION_REQUIRED
            - VERIFIED
            - REJECTED
            - EXPIRED
            - CANCELED
            - ERROR
        requirements:
          type: array
          items:
            $ref: '#/components/schemas/VerificationRequirementResponse'
        credentialId:
          type: object
          description: Credential produced by a VERIFIED session, once issued.
        createdAt:
          type: string
        updatedAt:
          type: string
        completedAt:
          type: object
      required:
        - id
        - kind
        - subjectType
        - subjectId
        - legalName
        - status
        - requirements
        - createdAt
        - updatedAt
    VerificationSessionListResponse:
      type: object
      properties:
        sessions:
          type: array
          items:
            $ref: '#/components/schemas/VerificationSessionResponse'
      required:
        - sessions
    CredentialResponse:
      type: object
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - PERSON_IDENTITY
            - BUSINESS_IDENTITY
            - DESTINATION_OWNERSHIP
        subjectType:
          type: string
          enum:
            - TENANT
            - TENANT_MEMBER
        subjectId:
          type: string
        status:
          type: string
          enum:
            - ACTIVE
            - EXPIRED
            - REVOKED
        legalName:
          type: string
        issuedAt:
          type: string
        expiresAt:
          type: object
        revokedAt:
          type: object
        revokedReason:
          type: object
      required:
        - id
        - kind
        - subjectType
        - subjectId
        - status
        - legalName
        - issuedAt
    CredentialListResponse:
      type: object
      properties:
        credentials:
          type: array
          items:
            $ref: '#/components/schemas/CredentialResponse'
      required:
        - credentials
    HandoffResponse:
      type: object
      properties:
        url:
          type: string
          description: SafeBank-hosted one-time handoff URL (QR payload).
        expiresAt:
          type: string
      required:
        - url
        - expiresAt
    CreatePayoutDestinationBody:
      type: object
      properties:
        type:
          type: string
          enum:
            - ONCHAIN_WALLET
            - PAYPAL
            - VENMO
            - US_BANK_ACH
            - SEPA
            - SAFEBANK_BALANCE
          description: >-
            Defaults to ONCHAIN_WALLET when an address is supplied (legacy
            shape).
        asset:
          type: string
          example: USDT
          description: Asset symbol (on-chain types).
        network:
          type: string
          example: tron
          description: Network key, lowercase (on-chain types).
        address:
          type: string
          description: Receiving address, stored verbatim (case-sensitive).
        email:
          type: string
          description: PayPal email.
        phone:
          type: string
          description: E.164 phone (PayPal/Venmo).
        handle:
          type: string
          description: Venmo handle (without @).
        routingNumber:
          type: string
          description: US ABA routing number (US_BANK_ACH).
        accountNumber:
          type: string
          description: US bank account number (US_BANK_ACH).
        iban:
          type: string
          description: IBAN (SEPA).
        country:
          type: string
          description: ISO-3166 alpha-2 country.
        currency:
          type: string
          description: ISO-4217 payout currency (fiat rails).
        label:
          type: string
          description: Display label override.
    PayoutDestinationResponse:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - STABLECOIN_ADDRESS
            - BANK_ACCOUNT
            - SAFEBANK_BALANCE
            - SAFEBANK_CARD
            - CASH_APP
            - PAYPAL
            - VENMO
            - US_BANK_ACH
            - US_BANK_INSTANT
            - SEPA
            - LOCAL_BANK
            - ONCHAIN_WALLET
            - SAFEBANK_SMART_ACCOUNT
        asset:
          type: object
        network:
          type: object
        address:
          type: object
          description: Full address — owner-read surface only (on-chain).
        provider:
          type: object
        country:
          type: object
        currency:
          type: object
        maskedLabel:
          type: string
        status:
          type: string
          enum:
            - PENDING_VERIFICATION
            - ACTIVE
            - ACTION_REQUIRED
            - EXPIRED
            - REVOKED
            - DISABLED
            - UNDER_REVIEW
        ownershipStatus:
          type: string
          enum:
            - DECLARED
            - VERIFIED
        verificationCredentialId:
          type: object
        priority:
          type: number
        allowAutomaticFallback:
          type: boolean
        isDefault:
          type: boolean
        consentedAt:
          type: object
        verifiedAt:
          type: object
        revokedAt:
          type: object
        createdAt:
          type: string
      required:
        - id
        - type
        - maskedLabel
        - status
        - ownershipStatus
        - priority
        - allowAutomaticFallback
        - isDefault
        - createdAt
    PayoutDestinationListResponse:
      type: object
      properties:
        destinations:
          type: array
          items:
            $ref: '#/components/schemas/PayoutDestinationResponse'
      required:
        - destinations
    UpdatePayoutDestinationBody:
      type: object
      properties:
        label:
          type: string
          description: Display label.
        priority:
          type: number
          description: Ordering preference among own destinations.
        allowAutomaticFallback:
          type: boolean
          description: >-
            Consent to automatic fallback routing to this destination (spec rule
            14).
    EvaluateEligibilityBody:
      type: object
      properties:
        action:
          type: string
          enum:
            - payments.create
            - invoices.pay
            - ramps.offramp
          example: payments.create
      required:
        - action
    EligibilityRequirementResponse:
      type: object
      properties:
        kind:
          type: string
          example: BUSINESS_IDENTITY
        satisfied:
          type: boolean
        credentialId:
          type: object
          nullable: true
      required:
        - kind
        - satisfied
        - credentialId
    EligibilityResponse:
      type: object
      properties:
        eligible:
          type: boolean
        mode:
          type: string
          enum:
            - 'off'
            - log
            - enforce
        requirements:
          type: array
          items:
            $ref: '#/components/schemas/EligibilityRequirementResponse'
      required:
        - eligible
        - mode
        - requirements
    FactorResponse:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
          enum:
            - EMAIL
            - SMS
            - TOTP
            - PRIVY_MFA
        status:
          type: string
          enum:
            - PENDING
            - ACTIVE
            - REVOKED
        label:
          type: object
          nullable: true
        hint:
          type: string
          description: Masked target — never the full address or number.
        verifiedAt:
          type: object
          nullable: true
        usableAt:
          type: object
          nullable: true
          description: >-
            When this factor may first be used to approve a transfer. New
            factors are held for 24h so an attacker with a live session cannot
            enroll their own device and use it immediately.
        usableNow:
          type: boolean
          description: False while inside the new-factor cooling-off window.
        lastUsedAt:
          type: object
          nullable: true
        createdAt:
          type: string
      required:
        - id
        - type
        - status
        - label
        - hint
        - verifiedAt
        - usableAt
        - usableNow
        - lastUsedAt
        - createdAt
    FactorListResponse:
      type: object
      properties:
        factors:
          type: array
          items:
            $ref: '#/components/schemas/FactorResponse'
        requiredFactors:
          type: number
          description: N in N-of-M — how many factors a challenge needs.
        usableFactors:
          type: number
          description: Factors currently usable (ACTIVE and past cooling-off).
      required:
        - factors
        - requiredFactors
        - usableFactors
    EnrollFactorBody:
      type: object
      properties:
        type:
          type: string
          enum:
            - EMAIL
            - SMS
            - TOTP
            - PRIVY_MFA
        email:
          type: string
          description: Required when type=EMAIL.
        phoneE164:
          type: string
          description: Required when type=SMS. E.164, e.g. +15551234567.
        label:
          type: string
          description: Optional friendly name, e.g. "work phone".
      required:
        - type
    EnrollFactorResponse:
      type: object
      properties:
        factorId:
          type: string
        type:
          type: string
          enum:
            - EMAIL
            - SMS
            - TOTP
            - PRIVY_MFA
        status:
          type: string
        hint:
          type: string
        secretBase32:
          type: string
          description: >-
            TOTP only. The shared secret, returned EXACTLY once — it is stored
            encrypted and no endpoint returns it again.
        otpauthUri:
          type: string
          description: TOTP only. otpauth:// URI for the QR code.
        signerAddress:
          type: string
          description: PRIVY_MFA only. The address the signature must recover to.
      required:
        - factorId
        - type
        - status
        - hint
    VerifyFactorBody:
      type: object
      properties:
        code:
          type: string
          description: 6-digit code (EMAIL / SMS / TOTP).
        signature:
          type: string
          description: EIP-712 signature (PRIVY_MFA).
    SendChallengeBody:
      type: object
      properties:
        factorId:
          type: string
          description: Which enrolled factor to deliver the code to.
      required:
        - factorId
    VerifyChallengeBody:
      type: object
      properties:
        factorId:
          type: string
        code:
          type: string
          description: 6-digit code (EMAIL / SMS / TOTP).
        signature:
          type: string
          description: EIP-712 signature (PRIVY_MFA).
      required:
        - factorId
    TransactionProtectionResponse:
      type: object
      properties:
        enabled:
          type: boolean
        coolingOffSeconds:
          type: number
          description: New-payee hold, in seconds. Floored at 86400 while enabled.
        thresholdUsd:
          type: string
          description: Transfers above this USD amount require a challenge.
        requiredFactors:
          type: number
        updatedAt:
          type: object
          nullable: true
      required:
        - enabled
        - coolingOffSeconds
        - thresholdUsd
        - requiredFactors
        - updatedAt
    UpdateTransactionProtectionBody:
      type: object
      properties:
        enabled:
          type: boolean
        coolingOffSeconds:
          type: number
          minimum: 86400
          description: Minimum 86400 (24h) while enabled.
        thresholdUsd:
          type: string
          example: '100'
        requiredFactors:
          type: number
          minimum: 1
    PaymentDestinationInput:
      type: object
      properties:
        type:
          type: string
          enum:
            - handle
            - email
            - address
            - wallet
            - contact
        handle:
          type: string
          description: Required when type=handle.
          example: volt-components
        label:
          type: string
          description: Required when type=contact — a saved address-book label.
          example: acme
        email:
          type: string
          description: Required when type=email.
        address:
          type: string
          description: Required when type=address (EIP-55).
        chainId:
          type: number
          description: Optional chain id for type=address; defaults to source.
        walletId:
          type: string
          description: Required when type=wallet.
      required:
        - type
    PaymentAmountInput:
      type: object
      properties:
        asset:
          type: string
          enum:
            - usdc
          description: Defaults to usdc.
        value:
          type: string
          description: Decimal amount (up to 6 dp).
          example: '25.00'
      required:
        - value
    CreatePaymentBody:
      type: object
      properties:
        sourceWalletId:
          type: string
          description: Wallet to pay from.
          example: w1
        destination:
          $ref: '#/components/schemas/PaymentDestinationInput'
        amount:
          $ref: '#/components/schemas/PaymentAmountInput'
        memo:
          type: string
          description: Free-text memo.
        ownerAddress:
          type: string
          description: >-
            Owner address to act as (must be a registered on-chain owner).
            Optional for single-owner wallets.
      required:
        - sourceWalletId
        - destination
        - amount
    PaymentResponse:
      type: object
      properties:
        id:
          type: string
        sourceWalletId:
          type: string
        destinationType:
          type: string
          enum:
            - handle
            - email
            - address
            - wallet
        destinationHandle:
          type: object
          nullable: true
        destinationAddress:
          type: string
        destinationChainId:
          type: number
        asset:
          type: string
          example: usdc
        amount:
          type: string
          description: Human decimal amount.
          example: '25.00'
        status:
          type: string
          enum:
            - CREATED
            - AWAITING_SIGNATURE
            - READY_TO_EXECUTE
            - EXECUTING
            - COMPLETED
            - FAILED
            - CANCELLED
        safeTxHash:
          type: object
          nullable: true
          description: Safe tx hash to sign.
        executedTxHash:
          type: object
          nullable: true
          description: On-chain execution tx hash.
        memo:
          type: object
          nullable: true
        failureReason:
          type: object
          nullable: true
        createdAt:
          type: string
      required:
        - id
        - sourceWalletId
        - destinationType
        - destinationHandle
        - destinationAddress
        - destinationChainId
        - asset
        - amount
        - status
        - safeTxHash
        - executedTxHash
        - memo
        - failureReason
        - createdAt
    PaymentListResponse:
      type: object
      properties:
        payments:
          type: array
          items:
            $ref: '#/components/schemas/PaymentResponse'
        total:
          type: number
        skip:
          type: number
        take:
          type: number
      required:
        - payments
        - total
        - skip
        - take
    SignPaymentBody:
      type: object
      properties:
        signature:
          type: string
          description: Raw secp256k1 signature over the safeTxHash.
      required:
        - signature
    ExecutePaymentBody:
      type: object
      properties:
        ownerAddress:
          type: string
          description: Owner address to act as. Optional for single-owner wallets.
    TreasuryBucketInput:
      type: object
      properties:
        name:
          type: string
          description: Bucket name, unique within the account.
          example: Operating
        kind:
          type: string
          enum:
            - OPERATING
            - RESERVE
            - YIELD
          description: What the bucket is for.
        targetBps:
          type: number
          description: >-
            Target share of the account's total USDC, in basis points (sum <=
            10000).
          example: 6000
        apyBps:
          type: number
          description: Simulated APY in basis points — YIELD buckets only.
          example: 450
      required:
        - name
        - kind
        - targetBps
    CreateTreasuryAccountBody:
      type: object
      properties:
        name:
          type: string
          description: Treasury name.
          example: Acme Treasury
        owners:
          description: Safe owners applied to every bucket wallet.
          type: array
          items:
            $ref: '#/components/schemas/WalletOwnerInput'
        threshold:
          type: number
          description: Signature threshold per bucket Safe. Defaults to 1.
        chainId:
          type: number
          description: EVM chain id. Defaults to Base Sepolia (84532).
        buckets:
          description: 1-8 buckets, one Safe each.
          type: array
          items:
            $ref: '#/components/schemas/TreasuryBucketInput'
      required:
        - name
        - owners
        - buckets
    TreasuryBucketView:
      type: object
      properties:
        bucketId:
          type: string
        name:
          type: string
        kind:
          type: string
          enum:
            - OPERATING
            - RESERVE
            - YIELD
        targetBps:
          type: number
        apyBps:
          type: object
          nullable: true
        walletId:
          type: string
        safeAddress:
          type: object
          nullable: true
          description: CREATE2 Safe address.
        walletStatus:
          type: string
          description: Bucket Safe deployment status.
        balanceUsd:
          type: object
          nullable: true
          description: >-
            Live USDC balance ("detail" reads only; null when the RPC read is
            unavailable).
        driftBps:
          type: object
          nullable: true
          description: >-
            Signed drift from targetBps in basis points of the account total
            (null when any bucket balance is unavailable).
        accruedYieldUsd:
          type: string
          description: Total simulated yield accrued to date.
        simulatedYield:
          type: boolean
          description: Always true — yield is simulated in the sandbox.
      required:
        - bucketId
        - name
        - kind
        - targetBps
        - apyBps
        - walletId
        - safeAddress
        - walletStatus
        - balanceUsd
        - driftBps
        - accruedYieldUsd
        - simulatedYield
    TreasuryAccountResponse:
      type: object
      properties:
        accountId:
          type: string
        name:
          type: string
        buckets:
          type: array
          items:
            $ref: '#/components/schemas/TreasuryBucketView'
        totalUsd:
          type: object
          nullable: true
          description: Sum of bucket balances (null when any bucket read is unavailable).
        createdAt:
          type: string
      required:
        - accountId
        - name
        - buckets
        - totalUsd
        - createdAt
    TreasuryAccountSummary:
      type: object
      properties:
        accountId:
          type: string
        name:
          type: string
        bucketCount:
          type: number
        createdAt:
          type: string
      required:
        - accountId
        - name
        - bucketCount
        - createdAt
    TreasuryAccountListResponse:
      type: object
      properties:
        accounts:
          type: array
          items:
            $ref: '#/components/schemas/TreasuryAccountSummary'
        total:
          type: number
      required:
        - accounts
        - total
    CreateTreasuryRuleBody:
      type: object
      properties:
        type:
          type: string
          enum:
            - SWEEP_EXCESS
            - TOP_UP
            - REBALANCE_TO_TARGET
        sourceBucketId:
          type: string
          description: Source bucket id (SWEEP_EXCESS / TOP_UP).
        destBucketId:
          type: string
          description: Destination bucket id (SWEEP_EXCESS / TOP_UP).
        thresholdUsd:
          type: string
          description: >-
            SWEEP_EXCESS: balance above this is swept. TOP_UP: trigger level
            (defaults to targetUsd).
          example: '1000.00'
        targetUsd:
          type: string
          description: 'TOP_UP: refill the destination back up to this level.'
          example: '500.00'
        intervalSeconds:
          type: number
          description: Minimum seconds between fires. Default 3600.
        enabled:
          type: boolean
          description: Rules are enabled by default.
      required:
        - type
    TreasuryRuleResponse:
      type: object
      properties:
        ruleId:
          type: string
        accountId:
          type: string
        type:
          type: string
          enum:
            - SWEEP_EXCESS
            - TOP_UP
            - REBALANCE_TO_TARGET
        sourceBucketId:
          type: object
          nullable: true
        destBucketId:
          type: object
          nullable: true
        thresholdUsd:
          type: object
          nullable: true
        targetUsd:
          type: object
          nullable: true
        intervalSeconds:
          type: number
        enabled:
          type: boolean
        lastFiredAt:
          type: object
          nullable: true
        createdAt:
          type: string
      required:
        - ruleId
        - accountId
        - type
        - sourceBucketId
        - destBucketId
        - thresholdUsd
        - targetUsd
        - intervalSeconds
        - enabled
        - lastFiredAt
        - createdAt
    TreasuryRuleListResponse:
      type: object
      properties:
        rules:
          type: array
          items:
            $ref: '#/components/schemas/TreasuryRuleResponse'
        total:
          type: number
      required:
        - rules
        - total
    UpdateTreasuryRuleBody:
      type: object
      properties:
        thresholdUsd:
          type: string
        targetUsd:
          type: string
        intervalSeconds:
          type: number
        enabled:
          type: boolean
    ManualSweepBody:
      type: object
      properties:
        sourceBucketId:
          type: string
          description: Bucket to move USDC out of.
        destBucketId:
          type: string
          description: Bucket to move USDC into.
        amountUsd:
          type: string
          description: USD amount to move.
          example: '250.00'
        ownerAddress:
          type: string
          description: >-
            Acting owner for the proposal (required when the Safe has multiple
            owners).
      required:
        - sourceBucketId
        - destBucketId
        - amountUsd
    TreasurySweepResponse:
      type: object
      properties:
        sweepId:
          type: string
        accountId:
          type: string
        ruleId:
          type: object
          nullable: true
          description: Rule that fired this sweep (null = manual).
        sourceBucketId:
          type: string
        destBucketId:
          type: string
        amountUsd:
          type: string
        status:
          type: string
          enum:
            - PROPOSED
            - SUPERSEDED
            - FAILED
          description: >-
            Proposal-leg status. The on-chain leg is the linked payment.
            SUPERSEDED = a newer sweep for the same bucket pair cancelled this
            one.
        paymentId:
          type: object
          nullable: true
          description: Payment carrying the on-chain transfer.
        paymentStatus:
          type: object
          nullable: true
          description: >-
            Live status of the linked payment (sign it via the payments API to
            execute the sweep).
        failureReason:
          type: object
          nullable: true
        createdAt:
          type: string
      required:
        - sweepId
        - accountId
        - ruleId
        - sourceBucketId
        - destBucketId
        - amountUsd
        - status
        - paymentId
        - paymentStatus
        - failureReason
        - createdAt
    TreasurySweepListResponse:
      type: object
      properties:
        sweeps:
          type: array
          items:
            $ref: '#/components/schemas/TreasurySweepResponse'
        total:
          type: number
      required:
        - sweeps
        - total
    CreateRampBody:
      type: object
      properties:
        walletId:
          type: string
          description: Wallet whose Safe is credited (on) or debited (off).
        direction:
          type: string
          enum:
            - 'on'
            - 'off'
          description: on = fiat->USDC, off = USDC->fiat.
        amount:
          type: string
          description: USD amount.
          example: '100.00'
        provider:
          type: string
          description: Ramp provider. Only `simulated` is live in the sandbox today.
          enum:
            - simulated
            - privy
            - stripe
            - visa
            - ousd
          default: simulated
      required:
        - walletId
        - direction
        - amount
    RampResponse:
      type: object
      properties:
        rampId:
          type: string
        walletId:
          type: string
        direction:
          type: string
          enum:
            - 'ON'
            - 'OFF'
        provider:
          type: string
          example: simulated
        amountUsd:
          type: string
        status:
          type: string
          enum:
            - SUBMITTED
            - CONFIRMED
            - FAILED
          description: >-
            ON confirms once the credit transfer lands. OFF stays SUBMITTED
            until owners sign the linked payment; the (simulated) fiat payout
            confirms when it executes.
        txHash:
          type: object
          nullable: true
          description: 'ON: the credit transfer tx hash.'
        paymentId:
          type: object
          nullable: true
          description: 'OFF: payment carrying the Safe debit.'
        paymentStatus:
          type: object
          nullable: true
          description: Live status of the linked payment (OFF only).
        fiatRef:
          type: object
          nullable: true
          description: Fiat-leg reference (simulated).
        simulated:
          type: boolean
          description: Always true — sandbox ramps simulate the fiat leg.
        failureReason:
          type: object
          nullable: true
        createdAt:
          type: string
      required:
        - rampId
        - walletId
        - direction
        - provider
        - amountUsd
        - status
        - txHash
        - paymentId
        - paymentStatus
        - fiatRef
        - simulated
        - failureReason
        - createdAt
    RampListResponse:
      type: object
      properties:
        ramps:
          type: array
          items:
            $ref: '#/components/schemas/RampResponse'
        total:
          type: number
      required:
        - ramps
        - total
    CreatePolicyBody:
      type: object
      properties:
        name:
          type: string
          description: Human name for the policy.
          example: AP bot guardrails
        document:
          type: object
          description: >-
            The policy document (schemaVersion 1): limits
            (per-txn/daily/weekly/monthly USD), mcc (blocked/allowed),
            counterparties (blocked). Compiled to an immutable versioned IR.
          example:
            schemaVersion: 1
            limits:
              perTransactionUsd: '250.00'
              dailyUsd: '1000.00'
            mcc:
              blocked:
                - '7995'
      required:
        - name
        - document
    PolicyResponse:
      type: object
      properties:
        policyId:
          type: string
        name:
          type: string
        status:
          type: string
          enum:
            - ACTIVE
            - ARCHIVED
        activeVersionId:
          type: object
          nullable: true
        activeVersion:
          type: object
          nullable: true
        createdAt:
          type: string
      required:
        - policyId
        - name
        - status
        - activeVersionId
        - activeVersion
        - createdAt
    PolicyListResponse:
      type: object
      properties:
        policies:
          type: array
          items:
            $ref: '#/components/schemas/PolicyResponse'
        total:
          type: number
      required:
        - policies
        - total
    EvaluatePolicyBody:
      type: object
      properties:
        policyId:
          type: string
          description: >-
            Evaluate a specific policy id (else the one attached to the
            subject).
        subjectType:
          type: string
          enum:
            - wallet
            - card
            - agent
            - treasury_account
          description: Resolve the attached policy.
        subjectId:
          type: string
        amountUsd:
          type: string
          description: Transaction USD amount to test.
          example: '120.00'
        mcc:
          type: string
          description: 4-digit MCC to test.
          example: '5411'
        counterparty:
          type: string
          description: Counterparty address to test.
      required:
        - amountUsd
    RuleTraceEntry:
      type: object
      properties:
        rule:
          type: string
          description: The rule that was checked.
          example: PER_TXN_LIMIT
        result:
          type: string
          enum:
            - pass
            - fail
            - skip
        detail:
          type: object
          nullable: true
          description: Why it passed/failed/was skipped.
      required:
        - rule
        - result
        - detail
    EvaluatePolicyResponse:
      type: object
      properties:
        allow:
          type: boolean
          description: true = the transaction is permitted by this policy.
        reason:
          type: object
          nullable: true
          description: Decline reason when allow=false.
        policyId:
          type: object
          nullable: true
          description: Policy evaluated (null = no policy resolved).
        policyVersion:
          type: object
          nullable: true
        sha256:
          type: object
          nullable: true
          description: IR hash the decision was made against.
        ruleTrace:
          description: Per-rule deterministic trace.
          type: array
          items:
            $ref: '#/components/schemas/RuleTraceEntry'
        decisionLogId:
          type: string
          description: DecisionLog row id (the audit anchor).
      required:
        - allow
        - reason
        - policyId
        - policyVersion
        - sha256
        - ruleTrace
        - decisionLogId
    CreatePolicyVersionBody:
      type: object
      properties:
        document:
          type: object
          description: A new document — committed as the next immutable version.
      required:
        - document
    PolicyVersionResponse:
      type: object
      properties:
        versionId:
          type: string
        version:
          type: number
        sha256:
          type: string
          description: sha256 of the canonical compiled IR.
        ir:
          type: object
          description: The compiled, normalized IR.
        createdAt:
          type: string
      required:
        - versionId
        - version
        - sha256
        - ir
        - createdAt
    PolicyVersionListResponse:
      type: object
      properties:
        versions:
          type: array
          items:
            $ref: '#/components/schemas/PolicyVersionResponse'
        total:
          type: number
      required:
        - versions
        - total
    AttachPolicyBody:
      type: object
      properties:
        subjectType:
          type: string
          enum:
            - wallet
            - card
            - agent
            - treasury_account
        subjectId:
          type: string
          description: Id of the wallet / card / agent / treasury account.
      required:
        - subjectType
        - subjectId
    PolicyAttachmentResponse:
      type: object
      properties:
        attachmentId:
          type: string
        policyId:
          type: string
        subjectType:
          type: string
          enum:
            - wallet
            - card
            - agent
            - treasury_account
        subjectId:
          type: string
        attachedAt:
          type: string
      required:
        - attachmentId
        - policyId
        - subjectType
        - subjectId
        - attachedAt
    CreateInvoiceBody:
      type: object
      properties:
        payeeName:
          type: string
          description: Human label for the payee.
          example: Volt Components
        description:
          type: string
          description: What the invoice is for.
        amountUsd:
          type: string
          description: Amount due, USD.
          example: '120.00'
        payerAddress:
          type: string
          description: Payer address (funds the hold Safe).
          example: 0x1111…
        payeeAddress:
          type: string
          description: Payee address (receives a release).
          example: 0x2222…
        arbiterAddress:
          type: string
          description: >-
            Platform arbiter address (tie-break owner). Defaults to the platform
            arbiter.
        chainId:
          type: number
          description: EVM chain id. Defaults to Base Sepolia (84532).
        expiresAt:
          type: string
          description: ISO-8601 auto-expiry for the unfunded invoice.
      required:
        - amountUsd
        - payerAddress
        - payeeAddress
    InvoiceResponse:
      type: object
      properties:
        invoiceId:
          type: string
        payeeName:
          type: object
          nullable: true
        description:
          type: object
          nullable: true
        amountUsd:
          type: string
        payerAddress:
          type: string
        payeeAddress:
          type: string
        arbiterAddress:
          type: string
        chainId:
          type: number
        status:
          type: string
          enum:
            - DRAFT
            - SENT
            - FUNDED
            - DELIVERED
            - RELEASED
            - REFUNDED
            - CANCELLED
            - EXPIRED
        holdWalletId:
          type: object
          nullable: true
          description: The 2-of-3 hold Safe id (once sent).
        holdAddress:
          type: object
          nullable: true
          description: CREATE2 address of the hold Safe.
        releasePaymentId:
          type: object
          nullable: true
        refundPaymentId:
          type: object
          nullable: true
        expiresAt:
          type: object
          nullable: true
        createdAt:
          type: string
      required:
        - invoiceId
        - payeeName
        - description
        - amountUsd
        - payerAddress
        - payeeAddress
        - arbiterAddress
        - chainId
        - status
        - holdWalletId
        - holdAddress
        - releasePaymentId
        - refundPaymentId
        - expiresAt
        - createdAt
    SendToInvoiceBody:
      type: object
      properties:
        toHandle:
          type: string
          description: Recipient's SafeBank @handle (or use toAddress).
        toAddress:
          type: string
          description: Recipient's wallet address (or use toHandle).
        amountUsd:
          type: string
          description: Amount due, USD.
          example: '120.00'
        payeeAddress:
          type: string
          description: Address that receives payment (the sender).
        payeeName:
          type: string
          description: Human label for the payee (bound by docHash).
        description:
          type: string
          description: What the bill is for (bound by docHash).
        chainId:
          type: number
          description: EVM chain id. Defaults to the payer wallet chain.
        senderAddress:
          type: string
          description: Address that SIGNED the authorization (a member key).
        signature:
          type: string
          description: EIP-712 InvoiceAuthorization signature.
        sigNonce:
          type: string
          description: Single-use replay nonce (uint256 as a decimal string).
        sigExpiresAt:
          type: string
          description: ISO-8601 authorization expiry.
        sigDocHash:
          type: string
          description: sha256 of the signed human-readable terms (bytes32).
        domainChainId:
          type: number
          description: EIP-712 domain chainId (defaults to the bill chainId).
        domainVerifyingContract:
          type: string
          description: EIP-712 domain verifyingContract anchor.
      required:
        - amountUsd
        - payeeAddress
        - senderAddress
        - signature
        - sigNonce
        - sigExpiresAt
    InboundInvoiceResponse:
      type: object
      properties:
        invoiceId:
          type: string
        amountUsd:
          type: string
        payerAddress:
          type: string
          description: The wallet expected to pay (ours).
        payeeAddress:
          type: string
          description: Where payment goes (the sender).
        payeeName:
          type: object
          nullable: true
        description:
          type: object
          nullable: true
        chainId:
          type: number
        payerTenantId:
          type: object
          nullable: true
        senderTenantId:
          type: object
          nullable: true
          description: Set only when VERIFIED.
        senderAddress:
          type: object
          nullable: true
        verificationStatus:
          type: string
          enum:
            - UNVERIFIED
            - VERIFIED
            - FAILED
        payerDecision:
          type: string
          nullable: true
          enum:
            - ACCEPTED
            - DECLINED
        payerPaymentId:
          type: object
          nullable: true
          description: The outbound debit once paid.
        sigExpiresAt:
          type: object
          nullable: true
        createdAt:
          type: string
      required:
        - invoiceId
        - amountUsd
        - payerAddress
        - payeeAddress
        - payeeName
        - description
        - chainId
        - payerTenantId
        - senderTenantId
        - senderAddress
        - verificationStatus
        - payerDecision
        - payerPaymentId
        - sigExpiresAt
        - createdAt
    InboundInvoiceListResponse:
      type: object
      properties:
        bills:
          type: array
          items:
            $ref: '#/components/schemas/InboundInvoiceResponse'
        total:
          type: number
      required:
        - bills
        - total
    InvoiceListResponse:
      type: object
      properties:
        invoices:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceResponse'
        total:
          type: number
      required:
        - invoices
        - total
    DeliverInvoiceBody:
      type: object
      properties:
        evidence:
          type: string
          description: Delivery evidence (note / URL / reference).
    SettleInvoiceBody:
      type: object
      properties:
        ownerAddress:
          type: string
          description: >-
            Acting owner for the release/refund proposal (defaults to the sole
            on-chain owner).
    PayBillBody:
      type: object
      properties:
        sourceWalletId:
          type: string
          description: The wallet to pay the bill from.
        ownerAddress:
          type: string
          description: Acting owner for the payment (if the wallet is M-of-N).
      required:
        - sourceWalletId
    PublicInvoiceResponse:
      type: object
      properties:
        invoiceId:
          type: string
        payeeName:
          type: object
          nullable: true
        description:
          type: object
          nullable: true
        amountUsd:
          type: string
        status:
          type: string
          enum:
            - DRAFT
            - SENT
            - FUNDED
            - DELIVERED
            - RELEASED
            - REFUNDED
            - CANCELLED
            - EXPIRED
        holdAddress:
          type: object
          nullable: true
          description: Send USDC here to fund the invoice.
        chainId:
          type: number
      required:
        - invoiceId
        - payeeName
        - description
        - amountUsd
        - status
        - holdAddress
        - chainId
    WalletBalanceLine:
      type: object
      properties:
        walletId:
          type: string
        name:
          type: object
          nullable: true
        address:
          type: object
          nullable: true
        chainId:
          type: object
          nullable: true
        status:
          type: string
        balanceUsd:
          type: object
          nullable: true
          description: >-
            Live USDC balance (2dp). null = undeployed or an unreadable RPC —
            never $0.
      required:
        - walletId
        - name
        - address
        - chainId
        - status
        - balanceUsd
    BillOwedLine:
      type: object
      properties:
        invoiceId:
          type: string
        amountUsd:
          type: string
        senderAddress:
          type: object
          nullable: true
        verificationStatus:
          type: string
          enum:
            - UNVERIFIED
            - VERIFIED
            - FAILED
        payerDecision:
          type: string
          nullable: true
          enum:
            - ACCEPTED
            - DECLINED
        createdAt:
          type: string
      required:
        - invoiceId
        - amountUsd
        - senderAddress
        - verificationStatus
        - payerDecision
        - createdAt
    BillsOwedSummary:
      type: object
      properties:
        count:
          type: number
          description: Number of payable bills owed.
        totalUsd:
          type: string
          description: Total owed across payable bills (USD).
        bills:
          type: array
          items:
            $ref: '#/components/schemas/BillOwedLine'
      required:
        - count
        - totalUsd
        - bills
    ActivityLine:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        amountUsd:
          type: string
        asset:
          type: string
        destination:
          type: string
        createdAt:
          type: string
      required:
        - id
        - status
        - amountUsd
        - asset
        - destination
        - createdAt
    SpendIntervalLine:
      type: object
      properties:
        interval:
          type: string
          enum:
            - daily
            - weekly
            - monthly
        capUsd:
          type: string
        spentUsd:
          type: string
        remainingUsd:
          type: string
      required:
        - interval
        - capUsd
        - spentUsd
        - remainingUsd
    AgentSpendLine:
      type: object
      properties:
        agentId:
          type: string
        name:
          type: object
          nullable: true
        active:
          type: boolean
        perTransactionUsd:
          type: object
          nullable: true
        limits:
          type: array
          items:
            $ref: '#/components/schemas/SpendIntervalLine'
      required:
        - agentId
        - name
        - active
        - perTransactionUsd
        - limits
    TenantSummaryResponse:
      type: object
      properties:
        netBalanceUsd:
          type: string
          description: Sum of readable wallet balances (USD, 2dp).
        balancePartial:
          type: boolean
          description: True if a deployed wallet balance was unreadable (net is a floor).
        wallets:
          type: array
          items:
            $ref: '#/components/schemas/WalletBalanceLine'
        billsOwed:
          $ref: '#/components/schemas/BillsOwedSummary'
        recentActivity:
          type: array
          items:
            $ref: '#/components/schemas/ActivityLine'
        agentSpend:
          type: array
          items:
            $ref: '#/components/schemas/AgentSpendLine'
        generatedAt:
          type: string
      required:
        - netBalanceUsd
        - balancePartial
        - wallets
        - billsOwed
        - recentActivity
        - agentSpend
        - generatedAt
    X402AuthorizeBody:
      type: object
      properties:
        scheme:
          type: string
          enum:
            - exact
            - upto
          description: x402 payment scheme.
        network:
          type: string
          example: eip155:84532
          description: CAIP-2 network id (EVM only).
        amount:
          type: string
          example: '10000'
          description: Atomic token units (for upto, the ceiling).
        asset:
          type: string
          description: Token contract address (USDC).
        payTo:
          type: string
          description: Recipient address (screened as the counterparty).
        maxTimeoutSeconds:
          type: number
          example: 60
        domainName:
          type: string
          description: EIP-712 domain name hint (402 extra.name).
          default: USD Coin
        domainVersion:
          type: string
          description: EIP-712 domain version hint (402 extra.version).
          default: '2'
        resource:
          type: string
          description: The protected resource URL (audit only).
      required:
        - scheme
        - network
        - amount
        - asset
        - payTo
        - maxTimeoutSeconds
    X402AuthorizationDto:
      type: object
      properties:
        from:
          type: string
        to:
          type: string
        value:
          type: string
        validAfter:
          type: string
        validBefore:
          type: string
        nonce:
          type: string
      required:
        - from
        - to
        - value
        - validAfter
        - validBefore
        - nonce
    X402AuthorizeResponse:
      type: object
      properties:
        xPayment:
          type: string
          description: Base64 `X-PAYMENT` header value to retry the request with.
        nonce:
          type: string
          description: EIP-3009 nonce — the reconciliation key.
        reservationId:
          type: string
        from:
          type: string
          description: The agent Safe the payment debits.
        payTo:
          type: string
        amountUsd:
          type: string
          description: Reserved USD amount (ceiling for upto).
        authorization:
          $ref: '#/components/schemas/X402AuthorizationDto'
      required:
        - xPayment
        - nonce
        - reservationId
        - from
        - payTo
        - amountUsd
        - authorization
    X402SettleBody:
      type: object
      properties:
        nonce:
          type: string
          description: The EIP-3009 nonce returned by authorize.
        txHash:
          type: string
          description: Settlement transaction hash.
        actualAmount:
          type: string
          description: Actual settled atomic units (upto ≤ ceiling). Defaults to reserved.
      required:
        - nonce
        - txHash
    X402SettleResponse:
      type: object
      properties:
        nonce:
          type: string
        status:
          type: string
          enum:
            - SETTLED
      required:
        - nonce
        - status
