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: Permara 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: Permara 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: Permara 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 Permara — subsequent
        authorizations decline immediately.
      operationId: freezeCard
      parameters:
        - name: id
          required: true
          in: path
          description: Permara 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: Permara 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/offramp/payments/{id}/simulate-funding:
    post:
      operationId: simulateFunding
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimulateFundingBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CorridorPaymentWithQuotesResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Simulate the funding deposit (sandbox only, sender only) — refused in
        production
      tags:
        - offramp
  /v1/approvals:
    get:
      operationId: approvals_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApprovalListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Approval requests in the tenant, newest first
      tags:
        - payments
  /v1/approvals/{id}/sign:
    post:
      operationId: sign
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignApprovalBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApprovalRequestResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Vote on an approval request (members only)
      tags:
        - payments
  /v1/approvals/{id}/reject:
    post:
      operationId: approvals_reject
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApprovalRequestResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Reject an approval request (any authorized rejection is final)
      tags:
        - payments
  /v1/payments:
    post:
      description: >-
        Normalized intents describe the payment
        (recipient/amount/funding/delivery) and let Permara route it. Legacy
        bodies (sourceWalletId + destination) continue to work with deprecation
        headers.
      operationId: unifiedPayments_create
      parameters:
        - name: x-safebank-2fa-challenge
          required: false
          in: header
          schema:
            type: string
        - name: idempotency-key
          required: true
          in: header
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/CreateUnifiedPaymentBody'
                - $ref: '#/components/schemas/CreatePaymentBody'
      responses:
        '202':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnifiedPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create a payment (normalized intent, or the deprecated Safe-debit shape)
      tags:
        - payments
    get:
      operationId: unifiedPayments_list
      parameters:
        - name: kind
          required: false
          in: query
          schema:
            enum:
              - legacy
              - unified
            type: string
        - 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 (legacy Safe-debit list by default; kind=unified for
        intents)
      tags:
        - payments
  /v1/payments/{id}:
    get:
      operationId: unifiedPayments_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnifiedPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Fetch one payment (unified intents and legacy payments share ids)
      tags:
        - payments
  /v1/payments/{id}/routes:
    get:
      operationId: unifiedPayments_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 route quotes + why withheld routes were excluded
      tags:
        - payments
  /v1/payments/{id}/select-route:
    post:
      operationId: unifiedPayments_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/UnifiedPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Select a route quote (recipient only)
      tags:
        - payments
  /v1/payments/{id}/approve:
    post:
      operationId: unifiedPayments_approve
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnifiedPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Approve a held payment (votes the caller's member on its approval
        request)
      tags:
        - payments
  /v1/payments/{id}/confirm:
    post:
      operationId: unifiedPayments_confirm
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnifiedPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Confirm the selected route — locks funds, starts execution
      tags:
        - payments
  /v1/payments/{id}/cancel:
    post:
      operationId: unifiedPayments_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/UnifiedPaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Cancel (unified intents and legacy payments share ids)
      tags:
        - payments
  /v1/payments/{id}/sign:
    post:
      operationId: unifiedPayments_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: '[legacy] Sign a Safe-debit payment'
      tags:
        - payments
  /v1/payments/{id}/execute:
    post:
      operationId: 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: '[legacy] Execute a threshold-met Safe-debit payment'
      tags:
        - payments
  /v1/payments/quote:
    post:
      description: >-
        What it costs for a payee (DID or handle) to receive an amount, given
        your funding asset. Exact-output by default: the recipient amount is
        guaranteed, the sender amount is solved. Quoting moves no funds and
        expires per the returned expiresAt.
      operationId: paymentsQuote_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Function'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentQuoteResponse'
        '404':
          description: PAYEE_NOT_FOUND
        '409':
          description: NO_ROUTE_AVAILABLE | QUOTE_UNSOLVABLE
        '422':
          description: PAYEE_UNVERIFIED | PAYEE_NOT_ROUTABLE
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Quote a payment to a DID
      tags:
        - payments
  /v1/webhooks/endpoints:
    post:
      operationId: createEndpoint
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookEndpointBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedWebhookEndpointResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Register a webhook endpoint (secret shown once)
      tags:
        - webhooks
    get:
      operationId: listEndpoints
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpointListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List webhook endpoints
      tags:
        - webhooks
  /v1/webhooks/endpoints/{id}:
    patch:
      operationId: updateEndpoint
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookEndpointBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpointResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Update an endpoint (status and/or subscribed events)
      tags:
        - webhooks
    delete:
      operationId: removeEndpoint
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpointResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Disable an endpoint (deliveries stop; history kept)
      tags:
        - webhooks
  /v1/webhooks/endpoints/{id}/rotate-secret:
    post:
      operationId: rotateSecret
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedWebhookEndpointResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Rotate the signing secret (new value shown once)
      tags:
        - webhooks
  /v1/webhooks/events:
    get:
      operationId: listEvents
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEventListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Emitted events, newest first
      tags:
        - webhooks
  /v1/webhooks/deliveries:
    get:
      operationId: listDeliveries
      parameters:
        - name: endpoint_id
          required: false
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDeliveryListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Delivery attempts (filter by endpoint_id)
      tags:
        - webhooks
  /v1/webhooks/deliveries/{id}/replay:
    post:
      operationId: replayDelivery
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDeliveryResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Re-queue one delivery (same stable event id)
      tags:
        - webhooks
  /v1/claims/{claimToken}:
    get:
      operationId: claims_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/dids/reservations:
    post:
      description: >-
        Returns the RESERVED did:permara identifier for the email or mobile
        number the invite will go to; sign the payment authorization against it
        and pay it with the same contact in `invite`. The same contact always
        returns the same identity.
      operationId: reserve
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReserveDidBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DidAliasResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Reserve an identity for someone not on Permara yet
      tags:
        - dids
  /v1/dids/me:
    get:
      operationId: dids_mine
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DidAliasListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The caller's payment identities
      tags:
        - dids
  /v1/dids/resolve:
    get:
      operationId: dids_resolve
      parameters:
        - name: did
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResolveDidResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Does this DID resolve to a payable identity?
      tags:
        - dids
  /v1/dids/{did}/did.json:
    get:
      operationId: document
      parameters:
        - name: did
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: W3C DID document
      summary: Public DID document (no endpoints, no PII)
      tags:
        - dids
  /1.0/identifiers/{did}:
    get:
      operationId: didResolution_resolve
      parameters:
        - name: did
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DidResolutionResultResponse'
      summary: W3C DID Resolution result for a did:permara identifier
      tags:
        - dids
  /v1/payment-profile:
    get:
      operationId: paymentProfile_get
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentProfileResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The caller's payment profile (identity + endpoints, masked)
      tags:
        - dids
  /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: tenants_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: keys_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/keys/{id}/signing-secret:
    post:
      description: >-
        Generates the HMAC signing secret — shown ONCE — and, when `required` is
        true, makes every @HighRiskOperation route demand a signed request from
        this key.
      operationId: setupSigning
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetupSigningBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SigningSecretResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Set up (or rotate) request signing for a 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: wallets_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: wallets_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/balance:
    get:
      description: >-
        Both holders on the configured chain in Demo USD (testnet) or USDC
        (mainnet). `ensure=true` provisions the org account and links the smart
        account when missing.
      operationId: read
      parameters:
        - name: ensure
          required: false
          in: query
          schema:
            enum:
              - 'true'
              - 'false'
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BalanceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: 'Where the money is: smart account and org account balances'
      tags:
        - balance
  /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:
    post:
      description: >-
        Mints Demo USD to the member's smart account (default) or the
        organization's Safe on a testnet chain. Same caps and rate limits as the
        wallet route; FAUCET_NOT_AVAILABLE on a mainnet chain.
      operationId: drip
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FundAccountBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DripResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Add Demo USD to one of your accounts
      tags:
        - faucet
    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: contacts_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/identities:
    get:
      operationId: identities_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List canonical identities in the tenant
      tags:
        - identity
    post:
      operationId: identities_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateIdentityBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get-or-create the identity for the tenant or one of its members
      tags:
        - identity
  /v1/identities/{id}:
    get:
      operationId: identities_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Fetch one identity
      tags:
        - identity
  /v1/identities/{id}/wallets:
    get:
      operationId: wallets
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletBindingListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Wallet bindings of an identity (all statuses — history included)
      tags:
        - identity
    post:
      operationId: identities_createWallet
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWalletBindingBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletBindingWithChallengeResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Start binding a wallet — returns the challenge the holder signs
      tags:
        - identity
  /v1/identities/{id}/wallets/{bindingId}/verify:
    post:
      operationId: verifyWallet
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: bindingId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyWalletBindingBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletBindingResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Prove wallet control (signature over the challenge); completes rotations
        too
      tags:
        - identity
  /v1/identities/{id}/wallets/{bindingId}/rotate:
    post:
      operationId: rotateWallet
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: bindingId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RotateWalletBindingBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletBindingWithChallengeResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Rotate to a new wallet — returns the NEW pending binding + challenge;
        verifying it retires the old one atomically (identity continuity)
      tags:
        - identity
  /v1/credentials/{id}/disclosures/{claim}:
    post:
      description: >-
        Returns {key, value, salt, leaf, proof[], root} for exactly one claim of
        the credential. A verifier checks it off-chain (verifyClaimProof) or
        on-chain (CredentialRegistry.verifyClaim) against the anchored root —
        every other claim stays a hash. 404 DISCLOSURE_NOT_AVAILABLE until the
        credential is anchored.
      operationId: disclose
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: claim
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: 'Selective disclosure: prove one claim against the on-chain claims root'
      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/credentials/{id}/status:
    get:
      operationId: statusOf
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialStatusResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Cached credential status with freshness anchors
      tags:
        - identity
  /v1/credentials/{id}/refresh:
    post:
      operationId: refresh
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialStatusResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Synchronously re-read the provider status (the high-value freshness
        path)
      tags:
        - identity
  /v1/credentials/{id}/artifact:
    get:
      operationId: artifact
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CredentialArtifactResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        The signed canonical credential document (holder read; 404 for legacy
        unsigned rows)
      tags:
        - identity
  /v1/verification-sessions/{id}/handoff:
    post:
      operationId: 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: payoutDestinations_verify
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyPayoutDestinationBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayoutDestinationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Verify destination ownership — signed EIP-712 proof (production, EVM) or
        sandbox stub
      tags:
        - identity
  /v1/credential-presentations/challenge:
    post:
      operationId: challenge
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePresentationChallengeBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresentationChallengeResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Mint a single-use presentation challenge (audience + nonce bound)
      tags:
        - identity
  /v1/credential-presentations/verify:
    post:
      operationId: presentations_verify
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyPresentationBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresentationVerdictResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Verify a wallet-signed presentation; consumes its challenge either way
      tags:
        - identity
  /v1/members/{memberId}/authorities:
    get:
      operationId: authorities_list
      parameters:
        - name: memberId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorityListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: A member's authority grants (all statuses)
      tags:
        - members
    post:
      operationId: grant
      parameters:
        - name: memberId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GrantAuthorityBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorityWithAttestationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Grant authority — returns the attestation the org Safe should sign
      tags:
        - members
  /v1/members/{memberId}/authorities/{authorityId}:
    patch:
      operationId: authorities_update
      parameters:
        - name: memberId
          required: true
          in: path
          schema:
            type: string
        - name: authorityId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAuthorityBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorityResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Suspend/reactivate or reschedule an authority
      tags:
        - members
    delete:
      operationId: authorities_revoke
      parameters:
        - name: memberId
          required: true
          in: path
          schema:
            type: string
        - name: authorityId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorityResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Revoke an authority and its backing credential atomically
      tags:
        - members
  /v1/members/{memberId}/authorities/{authorityId}/attest:
    post:
      operationId: attest
      parameters:
        - name: memberId
          required: true
          in: path
          schema:
            type: string
        - name: authorityId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AttestBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorityResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Attach the org-Safe EIP-1271 attestation (issues the
        AUTHORITY_DELEGATION credential)
      tags:
        - members
  /v1/payment-policy:
    get:
      operationId: paymentPolicy_get
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentPolicyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The ACTIVE payment policy (404 when none)
      tags:
        - policies
    put:
      operationId: put
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PutPaymentPolicyBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentPolicyWithAttestationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Supersede the active policy with a new version (returns the Safe
        attestation to sign)
      tags:
        - policies
  /v1/payment-policy/attest:
    post:
      operationId: paymentPolicy_attest
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AttestBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentPolicyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Attach the org-Safe attestation to the active policy version
      tags:
        - policies
  /v1/payment-policy/history:
    get:
      operationId: history
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentPolicyHistoryResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: All policy versions, newest first
      tags:
        - policies
  /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/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: policies_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: invoices_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: status_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
  /v1/agreements/templates:
    get:
      operationId: templates
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List built-in policy templates
      tags:
        - agreements
  /v1/agreements/drafts:
    post:
      operationId: createDraft
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAgreementDraftBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Create an agreement draft
      tags:
        - agreements
  /v1/agreements:
    get:
      operationId: agreements_list
      parameters:
        - name: limit
          required: true
          in: query
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List agreements for the tenant
      tags:
        - agreements
  /v1/agreements/{agreementId}:
    get:
      operationId: agreements_get
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get an agreement
      tags:
        - agreements
  /v1/agreements/{agreementId}/draft:
    post:
      operationId: updateDraft
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateAgreementDraftBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Update a draft (optimistic concurrency via expectedVersion)
      tags:
        - agreements
  /v1/agreements/{agreementId}/policy/compile:
    post:
      description: >-
        Percentages freeze to exact integer minor units; the compiled definition
        is content-addressed (canonicalHash) and stored as the next DRAFT policy
        version. Validation failures return structured issues and store nothing.
      operationId: compile
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompilePolicyBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompilePolicyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Compile an authored policy or template into the deterministic IR
      tags:
        - agreements
  /v1/agreements/{agreementId}/policy/validate:
    post:
      operationId: validate
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidatePolicyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Re-validate the latest compiled policy against current parties
      tags:
        - agreements
  /v1/agreements/{agreementId}/policy/simulate:
    post:
      description: >-
        Answers "what happens if this fact arrives / the deadline passes / a
        dispute opens" from the same deterministic evaluator that will govern
        real releases. Never moves state or money.
      operationId: agreements_simulate
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimulatePolicyBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimulatePolicyResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Simulate the latest policy under a what-if overlay
      tags:
        - agreements
  /v1/agreements/{agreementId}/funding-instructions:
    get:
      description: >-
        On-chain provider: escrow address, deal id, and ordered approve/fund
        calldata steps. Sandbox provider: a note pointing at simulate-funding.
        Chain events (not client claims) are the source of funding truth.
      operationId: fundingInstructions
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FundingInstructionsResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: How to protect the funds for this agreement
      tags:
        - agreements
  /v1/agreements/{agreementId}/simulate-funding:
    post:
      description: >-
        Available only with the mock protection provider (no escrow configured).
        Routes through the same funding accounting/FSM the on-chain indexer
        uses.
      operationId: agreementAcceptance_simulateFunding
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimulateFundingBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: 'Sandbox-only: simulate a protected-funds deposit'
      tags:
        - agreements
  /v1/agreements/{agreementId}/propose:
    post:
      description: >-
        Creates the content-addressed AgreementVersion binding parties, money,
        and the exact policy hash. Parties accept THIS hash — later edits create
        a new version and re-open acceptance.
      operationId: agreementAcceptance_propose
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementVersionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Freeze the draft into an immutable version and open acceptance
      tags:
        - agreements
  /v1/agreements/{agreementId}/parties/{partyKey}/bind:
    post:
      operationId: bindParty
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
        - name: partyKey
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BindPartyBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Bind a party slot to the calling tenant (counterparty joins)
      tags:
        - agreements
  /v1/agreements/{agreementId}/accept:
    post:
      description: >-
        The caller echoes the docHash + policyHash they reviewed; a mismatch
        (e.g. a concurrent amendment) is refused, so blind acceptance is
        impossible.
      operationId: agreementAcceptance_accept
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AcceptAgreementBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Accept the exact reviewed terms (hash-bound)
      tags:
        - agreements
  /v1/agreements/{agreementId}/authorizations/preview:
    post:
      description: >-
        Hashes are server-derived from the stored agreement/policy versions —
        the wallet signs what Permara will verify, nothing looser.
      operationId: previewAuthorization
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreviewAuthorizationBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorizationPreviewResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Get the exact EIP-712 typed-data the payer must sign
      tags:
        - agreements
  /v1/agreements/{agreementId}/authorizations:
    post:
      description: >-
        ACTIVATION scope preauthorizes every PREAUTHORIZED_CONDITIONAL stage of
        the exact policy version; RELEASE scope approves one stage just-in-time.
        Replay-fenced by nonce and digest.
      operationId: submitAuthorization
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitAuthorizationBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorizationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Submit a signed EIP-712 AgreementAuthorization
      tags:
        - agreements
  /v1/agreements/{agreementId}/confirmations:
    post:
      description: >-
        Creates an observation + attestation and runs deterministic trust
        evaluation. An accepted fact never moves money — it only feeds the
        policy evaluator. Contradictory attestations pause the stage for review;
        arrival order never decides.
      operationId: agreementAcceptance_confirm
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitConfirmationBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitConfirmationResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Submit a party confirmation (or refutation) for a proof requirement
      tags:
        - agreements
  /v1/agreements/{agreementId}/attestations/{attestationId}/revoke:
    post:
      description: >-
        Dependent accepted facts flip to REVOKED and the agreement re-evaluates
        — a stage that lost its proof returns to waiting.
      operationId: revokeAttestation
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
        - name: attestationId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RevokeAttestationBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Revoke your own attestation
      tags:
        - agreements
  /v1/agreements/{agreementId}/policy-draft:
    get:
      description: >-
        What the conversation renders as the "Do not pay unless —" card, the
        rules card and the quorum stamps. Read-only; the decision itself stays
        with decisions/latest.
      operationId: policyDraftRead
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementPolicyDraftResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        The policy in plain sentences: conditions, rules and who still has to
        sign
      tags:
        - agreements
  /v1/agreements/{agreementId}/decisions/latest:
    get:
      operationId: latestDecision
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementDecisionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The latest deterministic policy decision ("why is this waiting?")
      tags:
        - agreements
  /v1/agreements/{agreementId}/disputes:
    post:
      description: >-
        A dispute can never claw back settled value; it only pauses stages that
        have not paid out. Resolution is mutual (the counterparty decides), and
        a resolved dispute never auto-releases: conditions still govern.
      operationId: openDispute
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenDisputeBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DisputeResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Open a dispute — affected unreleased stages freeze immediately
      tags:
        - agreements
  /v1/agreements/{agreementId}/disputes/{disputeId}/resolve:
    post:
      operationId: resolveDispute
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
        - name: disputeId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResolveDisputeBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: 'Resolve a dispute (mutual: only the counterparty of the opener may)'
      tags:
        - agreements
  /v1/agreements/{agreementId}/card:
    get:
      description: >-
        Generated deterministically from the same structured policy the parties
        accept (never AI prose), with openQuestions driving the guided/chat
        creation flow. All clients render this one JSON contract.
      operationId: card
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The Agreement Card — canonical plain-language projection
      tags:
        - agreements
  /v1/agreements/{agreementId}/timeline:
    get:
      description: >-
        A role-aware projection of the append-only event log, plus per-step
        unsatisfied conditions derived from the latest deterministic decision —
        exact reasons, never guesses.
      operationId: agreementAcceptance_timeline
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementTimelineResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Plain-language timeline + "why is this waiting?"
      tags:
        - agreements
  /v1/agreements/{agreementId}/verified-payment-record:
    get:
      description: >-
        Content-addressed record of what was agreed (hashes), proven
        (fact/attestation hashes), authorized (EIP-712 digests), and settled —
        without private routing internals. Generated once on first request after
        completion.
      operationId: verifiedPaymentRecord
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The completed agreement’s portable proof
      tags:
        - agreements
  /v1/agreements/{agreementId}/activate:
    post:
      description: >-
        Freezes the policy ACTIVE and spawns the runtime release rows and
        timeout timers. Releases stay withheld until funding covers them —
        activation never moves money.
      operationId: activate
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: 'Activate: acceptances complete + valid ACTIVATION authorization'
      tags:
        - agreements
  /v1/agreements/{agreementId}/parties/{partyKey}/share-link:
    post:
      operationId: createShareLink
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
        - name: partyKey
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShareLinkResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Mint a single-use share link for an unbound party slot
      tags:
        - agreements
  /v1/agreements/claim/{token}:
    get:
      operationId: agreementShare_resolve
      parameters:
        - name: token
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementClaimPreviewResponse'
      summary: >-
        Resolve a share link to the exact Agreement Card (public; token =
        capability)
      tags:
        - agreements
  /v1/agreements/claim/{token}/join:
    post:
      operationId: join
      parameters:
        - name: token
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementJoinResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Consume the share link and bind its party slot to the calling tenant
      tags:
        - agreements
  /v1/agreements/{agreementId}/event-sources:
    post:
      operationId: agreementEventSource_create
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateEventSourceBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateEventSourceResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Register an event source (secret shown once)
      tags:
        - agreements
    get:
      operationId: agreementEventSource_list
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventSourceListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List event sources for an agreement (no secrets)
      tags:
        - agreements
  /v1/agreements/{agreementId}/event-sources/{sourceId}/rotate-secret:
    post:
      operationId: agreementEventSource_rotate
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
        - name: sourceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RotateSecretResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Rotate the HMAC secret (new value shown once)
      tags:
        - agreements
  /v1/agreements/{agreementId}/event-sources/{sourceId}/revoke:
    post:
      operationId: agreementEventSource_revoke
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
        - name: sourceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Revoke an event source (ingest rejects immediately)
      tags:
        - agreements
  /v1/agreements/{agreementId}/event-sources/{sourceId}/deliveries:
    get:
      operationId: deliveries
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
        - name: sourceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventSourceDeliveriesResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Delivery log — every ingest attempt and its outcome
      tags:
        - agreements
  /v1/agreements/{agreementId}/event-sources/{sourceId}/test:
    post:
      operationId: test
      parameters:
        - name: agreementId
          required: true
          in: path
          schema:
            type: string
        - name: sourceId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventSourceTestSampleResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Test console: a ready-to-send signed sample request for the current
        secret
      tags:
        - agreements
  /v1/event-sources/{endpointId}/events:
    post:
      description: >-
        Body: {"nonce": "<unique, 8..64 chars>", "confirmed": true|false,
        "note"?: string}. Creates an Observation/Attestation for the registered
        proof requirement — the deterministic policy engine decides everything
        after that.
      operationId: agreementEventIngest_ingest
      parameters:
        - name: endpointId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IngestEventResponse'
      summary: Deliver a signed external event (HMAC-authenticated, replay-fenced)
      tags:
        - agreements
  /v1/agreements/chat/tools:
    get:
      operationId: tools
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatToolManifestResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        The tool manifest this principal may invoke through chat
        (registry-derived, scope-filtered)
      tags:
        - agreements
  /v1/agreements/chat/sessions:
    get:
      description: >-
        The first page carries every pinned conversation, then the newest
        unpinned ones; `before` pages the unpinned segment only.
      operationId: agreementsAi_listSessions
      parameters:
        - name: limit
          required: false
          in: query
          description: 1..100, default 50.
          schema:
            type: string
        - name: before
          required: false
          in: query
          description: >-
            ISO timestamp from a previous page's nextBefore. Pages the unpinned
            segment; pinned conversations all ride the first page.
          schema:
            type: string
        - name: pinned
          required: false
          in: query
          description: true = pinned conversations only; false = unpinned only.
          schema:
            type: boolean
        - name: kind
          required: false
          in: query
          description: Only conversations of this kind.
          schema:
            enum:
              - pay
              - agreement
              - invoice
              - financing
              - general
            type: string
        - name: attention
          required: false
          in: query
          description: Only conversations waiting on the person for this reason.
          schema:
            enum:
              - SIGNATURE
              - CLARIFICATION
              - REVIEW
              - OFFER
            type: string
        - name: mine
          required: false
          in: query
          description: >-
            true = only the conversations this member opened. An API key has no
            member, so it receives no conversations.
          schema:
            type: boolean
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatSessionListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        This tenant's conversations, pinned first, then newest activity (the
        Home list)
      tags:
        - agreements
    post:
      operationId: createSession
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateChatSessionBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatSessionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Start a conversation. With `text`, the person's first words are sent as
        the first turn at once (stream events from cursor 0).
      tags:
        - agreements
  /v1/agreements/chat/sessions/{sessionId}:
    patch:
      operationId: patchSession
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatchChatSessionBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatSessionSummaryResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Rename or pin a conversation
      tags:
        - agreements
    get:
      operationId: agreementsAi_getSession
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatTranscriptResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Fetch a chat session transcript
      tags:
        - agreements
  /v1/agreements/chat/sessions/{sessionId}/payment:
    post:
      operationId: linkPayment
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkChatPaymentBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatSessionSummaryResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Record the payment the person signed from this conversation (the app
        calls this after POST /v1/offramp/payments; the assistant never creates
        one)
      tags:
        - agreements
  /v1/agreements/chat/sessions/{sessionId}/messages:
    post:
      operationId: sendMessage
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendChatMessageBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendChatMessageResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Send a message; the assistant drafts through server-validated tools
      tags:
        - agreements
  /v1/agreements/chat/sessions/{sessionId}/events:
    get:
      operationId: agreementsAi_listEvents
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: cursor
          required: false
          in: query
          description: Last seq already seen (default 0).
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ChatEventResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Persisted turn events after a cursor (resume/replay); the same log the
        SSE stream delivers live
      tags:
        - agreements
  /v1/agreements/chat/sessions/{sessionId}/events/stream:
    get:
      operationId: streamEvents
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: cursor
          required: false
          in: query
          description: Last seq already seen (default 0).
          schema:
            type: string
      responses:
        '200':
          description: >-
            text/event-stream: one frame per event (id=seq, event=type,
            data=ChatEventResponse JSON) plus heartbeat frames.
          content:
            text/event-stream:
              schema:
                type: string
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Server-sent turn events (text/event-stream): id=seq, event=type,
        data=ChatEventResponse. Reconnect with ?cursor=<last seq>.
      tags:
        - agreements
  /v1/agreements/chat/sessions/{sessionId}/turns/{clientTurnId}/cancel:
    post:
      operationId: cancelTurn
      parameters:
        - name: sessionId
          required: true
          in: path
          schema:
            type: string
        - name: clientTurnId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CancelChatTurnResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Cancel generation for an in-flight turn (needs its clientTurnId). Never
        cancels a submitted operation.
      tags:
        - agreements
  /v1/payables/address:
    get:
      operationId: address
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayablesAddressResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The tenant's inbound payables address (minted on first read)
      tags:
        - payables
  /v1/payables/inbound/simulate:
    post:
      description: >-
        Runs the exact ingestion pipeline (scan → OCR → extraction → risk flags)
        without SES. Production requires PAYABLES_INGEST_SIMULATE=true.
      operationId: payables_simulate
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimulateInboundBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimulateInboundResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: 'Sandbox: deliver an email to your own payables address'
      tags:
        - payables
  /v1/payables/uploads:
    post:
      description: >-
        Organization-scoped. Runs the same pipeline as inbound mail (scan → OCR
        → extraction → risk flags). Returns an uploadId the assistant can read
        with `prepare_document`; bytes are never exposed back.
      operationId: upload
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadDocumentBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadDocumentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Upload a document (invoice/PO) for extraction — the authorized upload
        session behind chat
      tags:
        - payables
  /v1/payables/messages:
    get:
      operationId: messages
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InboundMessagesResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Inbound messages for the tenant
      tags:
        - payables
  /v1/payables/extractions:
    get:
      operationId: extractions
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExtractionListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Invoice extractions with their risk flags
      tags:
        - payables
  /v1/payables/extractions/{extractionId}:
    get:
      operationId: extraction
      parameters:
        - name: extractionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExtractionDetailResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        One extraction: the render-ready facts, per-field confidence,
        corrections, flags
      tags:
        - payables
  /v1/payables/extractions/{extractionId}/corrections:
    post:
      operationId: correct
      parameters:
        - name: extractionId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CorrectExtractionBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExtractionDetailResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Correct an extracted field (audited; confidence becomes 1)
      tags:
        - payables
  /v1/payables/extractions/{extractionId}/approve:
    post:
      description: Blocked while risk flags are OPEN or critical fields are missing.
      operationId: payables_approve
      parameters:
        - name: extractionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApproveExtractionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Approve the reviewed extraction → agreement draft (INVOICE, or PURCHASE
        for a purchase order)
      tags:
        - payables
  /v1/payables/extractions/{extractionId}/reject:
    post:
      operationId: payables_reject
      parameters:
        - name: extractionId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Reject an extraction (dismisses its open flags)
      tags:
        - payables
  /v1/payables/flags/{flagId}/resolve:
    post:
      operationId: resolveFlag
      parameters:
        - name: flagId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResolveFlagBody'
      responses:
        '200':
          description: ''
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Resolve or dismiss an open risk flag (audited)
      tags:
        - payables
  /v1/lending/pools:
    post:
      operationId: lendingPools_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePoolBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoolResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Deploy a demo lending pool cohort (spec default economics)
      tags:
        - lending
    get:
      operationId: lendingPools_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoolListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: List pools with live chain state
      tags:
        - lending
  /v1/lending/pools/{poolId}:
    get:
      operationId: lendingPools_get
      parameters:
        - name: poolId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoolResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Pool detail with live chain state
      tags:
        - lending
  /v1/lending/pools/{poolId}/positions:
    get:
      operationId: position
      parameters:
        - name: poolId
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PositionResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The calling tenant's LP position in this pool
      tags:
        - lending
  /v1/lending/pools/{poolId}/deposits:
    post:
      description: >-
        Test funds only. Contributions become locked when subscriptions close;
        repayment is not guaranteed.
      operationId: deposit
      parameters:
        - name: poolId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AmountBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TxResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Subscribe test capital during OPEN
      tags:
        - lending
  /v1/lending/pools/{poolId}/withdrawals:
    post:
      operationId: lendingPools_withdraw
      parameters:
        - name: poolId
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AmountBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TxResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Withdraw at par strictly before the subscription cutoff
      tags:
        - lending
  /v1/lending/pools/{poolId}/finalize:
    post:
      operationId: lendingPools_finalize
      parameters:
        - name: poolId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TxResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Permissionless subscription finalization after the cutoff
      tags:
        - lending
  /v1/lending/pools/{poolId}/runoff:
    post:
      operationId: runoff
      parameters:
        - name: poolId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TxResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Permissionless runoff transition after the origination cutoff
      tags:
        - lending
  /v1/lending/pools/{poolId}/close:
    post:
      operationId: close
      parameters:
        - name: poolId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TxResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Permissionless closure once every loan is repaid or written down
      tags:
        - lending
  /v1/lending/pools/{poolId}/claims:
    post:
      operationId: lendingPools_claim
      parameters:
        - name: poolId
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClaimResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Claim the tenant's currently claimable proceeds (paid to the recorded
        owner wallet)
      tags:
        - lending
  /v1/lending/pos:
    post:
      operationId: registerPo
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterPoBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Register an APPROVED payables extraction as a financeable PO
      tags:
        - lending
    get:
      operationId: listPos
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Purchase orders the tenant borrows against or supplies
      tags:
        - lending
  /v1/lending/pos/{id}/accept:
    post:
      operationId: acceptPo
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AcceptPoBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PoResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: 'Supplier acceptance: sign the exact PO and payout wallet'
      tags:
        - lending
  /v1/lending/eligibility:
    post:
      description: >-
        Issuer-attested MVP: the platform signs an EIP-712 eligibility permit
        over EXPLICIT synthetic evidence. The pool verifies the signature
        on-chain; a credential alone never moves funds.
      operationId: issueEligibility
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueEligibilityBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EligibilityResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Issue the verifiable financial eligibility credential (synthetic
        evidence)
      tags:
        - lending
    get:
      operationId: listEligibility
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EligibilityResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The tenant's eligibility credentials
      tags:
        - lending
  /v1/lending/offers:
    post:
      operationId: createOffer
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOfferBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Request an underwriter-signed offer on a registered PO
      tags:
        - lending
  /v1/lending/offers/{id}/accept:
    post:
      operationId: acceptOffer
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Borrower acceptance binding the offer's exact digest
      tags:
        - lending
  /v1/lending/offers/{id}:
    get:
      operationId: getOffer
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OfferResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Offer detail
      tags:
        - lending
  /v1/lending/loans:
    post:
      operationId: finance
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoanResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Draw the loan: atomic four-signature origination paying the supplier
        directly
      tags:
        - lending
    get:
      operationId: listLoans
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoanListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: The tenant's loans
      tags:
        - lending
  /v1/lending/loans/{id}:
    get:
      operationId: getLoan
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoanResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Loan detail with live debt state
      tags:
        - lending
  /v1/lending/loans/{id}/repayments:
    post:
      description: >-
        Available during pauses, delinquency, default, and after closure — a
        post-default payment is a recovery for the same frozen cohort.
        Third-party payers gain nothing.
      operationId: repay
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RepayBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RepaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Repay up to a maximum: interest first, then principal, routed to the
        pool
      tags:
        - lending
  /v1/lending/loans/{id}/default-checkpoint:
    post:
      operationId: checkpointDefault
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TxResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Permissionless deterministic default recognition after grace
      tags:
        - lending
  /v1/trade/orders:
    post:
      operationId: trade_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTradeOrderBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeOrderResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Draft a purchase order (structured terms, or seeded from an uploaded
        document)
      tags:
        - trade
    get:
      operationId: trade_list
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeOrderListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Trade orders where you are buyer or seller
      tags:
        - trade
  /v1/trade/orders/{id}:
    get:
      operationId: trade_get
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeOrderResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Trade order detail
      tags:
        - trade
    put:
      operationId: trade_update
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTradeOrderBody'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeOrderResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Buyer edits the draft — bumps the version, voids prior signatures
      tags:
        - trade
  /v1/trade/orders/{id}/propose:
    post:
      operationId: trade_propose
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeOrderResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Buyer signs the exact version and proposes it to the seller
      tags:
        - trade
  /v1/trade/orders/{id}/review:
    post:
      operationId: review
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReviewTradeOrderBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeOrderResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Seller review: accept (countersign → Verified Sales Order), request
        changes, or reject
      tags:
        - trade
  /v1/trade/orders/{id}/deliveries:
    post:
      operationId: trade_deliver
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliverBody'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeDeliveryResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Seller delivers against the sales order with attached evidence
      tags:
        - trade
    get:
      operationId: trade_deliveries
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TradeDeliveryResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Deliveries recorded against an order
      tags:
        - trade
  /v1/trade/deliveries/{id}/accept:
    post:
      operationId: acceptDelivery
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeDeliveryResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Buyer accepts the delivery — signs the acceptance and triggers invoice
        creation
      tags:
        - trade
  /v1/trade/invoices:
    get:
      operationId: invoices
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradeInvoiceListResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: Invoices generated from your sales orders
      tags:
        - trade
  /v1/trade/invoices/{id}/pay:
    post:
      description: >-
        Re-verifies the whole document chain — identities, mutual sales-order
        signatures against the exact content, delivery acceptance, invoice match
        — before any funds move.
      operationId: trade_pay
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TradePaymentResponse'
      security:
        - apiKeyAuth: []
        - privyBearer: []
      summary: >-
        Authorize and route the payment: to the financier (loan repayment) when
        financed, else to the seller
      tags:
        - trade
info:
  title: Permara 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↔Permara 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
    CreateAdminUserBody:
      type: object
      properties: {}
    UpdateAdminUserBody:
      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: {}
    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
      required:
        - walletAddress
        - signature
        - message
    CardholderResponse:
      type: object
      properties:
        cardholderId:
          type: string
          description: >-
            Permara 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: >-
            Permara 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: Permara 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: Permara 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: Permara 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@permara.example.com
        phone:
          type: string
          example: +1-800-555-0199
        mailingAddress:
          type: string
          example: Permara 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 Permara 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
        state:
          type: string
        reasonCode:
          type: string
        openedByPartyKey:
          type: string
        openedAt:
          type: string
      required:
        - id
        - state
        - reasonCode
        - openedByPartyKey
        - openedAt
    DisputeListResponse:
      type: object
      properties:
        disputes:
          type: array
          items:
            $ref: '#/components/schemas/DisputeResponse'
        total:
          type: number
          description: Every matching dispute, not only this page.
          example: 3
        nextCursor:
          type: object
          description: 'Operator lists only: the id to continue from; null on the last page.'
          nullable: true
      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:
        resolution:
          type: string
          enum:
            - RELEASE
            - REFUND
        note:
          type: string
      required:
        - resolution
    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
    InvestLinkStatusResponse:
      type: object
      properties:
        deckTitle:
          type: string
          description: Presentation title shown on the gate
        policy:
          type: string
          enum:
            - SINGLE_EMAIL
            - DOMAIN_ALLOWLIST
            - ANY_VERIFIED_EMAIL
        noticeVersion:
          type: number
          description: Current privacy-notice version to acknowledge
        noticeBody:
          type: string
          description: Markdown body of the privacy notice
      required:
        - deckTitle
        - policy
        - noticeVersion
        - noticeBody
    InvestRequestCodeBody:
      type: object
      properties:
        email:
          type: string
          description: Work email of the viewer
        turnstileToken:
          type: string
          description: Cloudflare Turnstile token (validated server-side)
      required:
        - email
        - turnstileToken
    InvestRequestCodeResponse:
      type: object
      properties:
        status:
          type: string
          description: >-
            Always the same value regardless of whether the email exists, is
            authorized, or a code was actually sent — this endpoint is
            deliberately not an oracle.
        browserToken:
          type: string
          description: Opaque browser-challenge value the BFF stores as an HttpOnly cookie
        expiresInSeconds:
          type: number
          description: Seconds until the code expires
      required:
        - status
        - browserToken
        - expiresInSeconds
    InvestResendCodeBody:
      type: object
      properties:
        browserToken:
          type: string
          description: Browser-challenge value from the gate cookie
      required:
        - browserToken
    InvestVerifyCodeBody:
      type: object
      properties:
        browserToken:
          type: string
          description: Browser-challenge value from the gate cookie
        otpCode:
          type: string
          description: 6-digit one-time code from the email
        acceptNotice:
          type: boolean
          description: Viewer acknowledged the privacy/tracking notice
        timeZone:
          type: string
          description: IANA timezone reported by the browser
      required:
        - browserToken
        - otpCode
        - acceptNotice
    InvestSessionResponse:
      type: object
      properties:
        status:
          type: string
        sessionToken:
          type: string
          description: Bearer session token — BFF-only, becomes the HttpOnly session cookie
        deviceToken:
          type: string
          description: Trusted-device token — BFF-only, becomes the HttpOnly device cookie
        expiresAt:
          type: string
          description: Session expiry (absolute TTL, no sliding renewal)
        sessionRef:
          type: string
          description: Short public session reference (also in the watermark)
      required:
        - status
        - sessionToken
        - deviceToken
        - expiresAt
        - sessionRef
    InvestTrustedSessionBody:
      type: object
      properties:
        linkToken:
          type: string
          description: Raw access-link token from the /d/{token} URL
        deviceToken:
          type: string
          description: Trusted-device value from the device cookie
        timeZone:
          type: string
          description: IANA timezone reported by the browser
      required:
        - linkToken
        - deviceToken
    InvestCreateLinkBody:
      type: object
      properties:
        deckId:
          type: string
        label:
          type: string
          description: Internal label, e.g. "Maya Chen — a16z"
        policy:
          type: string
          enum:
            - SINGLE_EMAIL
            - DOMAIN_ALLOWLIST
            - ANY_VERIFIED_EMAIL
        allowedEmail:
          type: string
          description: Required when policy=SINGLE_EMAIL
        allowedDomains:
          description: >-
            Required when policy=DOMAIN_ALLOWLIST — bare domains, e.g.
            ["a16z.com"]
          type: array
          items:
            type: string
        pinnedVersionId:
          type: string
          description: 'Pin to a specific deck version id (default: follow latest published)'
        expiresAt:
          type: string
        maxVerifiedViewers:
          type: number
          minimum: 1
        watermarkEnabled:
          type: boolean
          default: true
        ownerAdminId:
          type: string
          description: AdminUser id of the relationship owner
        sendToEmail:
          type: string
          description: >-
            Email the share URL immediately after creation (the URL exists only
            once — sending later is impossible by design)
      required:
        - deckId
        - label
        - policy
    InvestCreateLinkResponse:
      type: object
      properties:
        id:
          type: string
        deckId:
          type: string
        label:
          type: string
        policy:
          type: string
        tokenLast4:
          type: string
          description: Last 4 characters of the raw token, for recognition only
        allowedEmail:
          type: object
          description: Decrypted bound email (SINGLE_EMAIL links)
        allowedDomains:
          type: array
          items:
            type: string
        expiresAt:
          type: object
        revokedAt:
          type: object
        watermarkEnabled:
          type: boolean
        createdAt:
          type: string
        viewerCount:
          type: number
          description: Verified viewer identities on this link
        shareUrl:
          type: string
          description: >-
            The full share URL, shown exactly once at creation — only a hash is
            stored.
        sentToRecipient:
          type: boolean
          description: Whether the URL was emailed to sendToEmail at creation
      required:
        - id
        - deckId
        - label
        - policy
        - tokenLast4
        - watermarkEnabled
        - createdAt
        - viewerCount
        - shareUrl
        - sentToRecipient
    InvestLinkAdminResponse:
      type: object
      properties:
        id:
          type: string
        deckId:
          type: string
        label:
          type: string
        policy:
          type: string
        tokenLast4:
          type: string
          description: Last 4 characters of the raw token, for recognition only
        allowedEmail:
          type: object
          description: Decrypted bound email (SINGLE_EMAIL links)
        allowedDomains:
          type: array
          items:
            type: string
        expiresAt:
          type: object
        revokedAt:
          type: object
        watermarkEnabled:
          type: boolean
        createdAt:
          type: string
        viewerCount:
          type: number
          description: Verified viewer identities on this link
      required:
        - id
        - deckId
        - label
        - policy
        - tokenLast4
        - watermarkEnabled
        - createdAt
        - viewerCount
    InvestUpdateLinkBody:
      type: object
      properties:
        label:
          type: string
        expiresAt:
          type: object
        maxVerifiedViewers:
          type: number
          minimum: 1
        watermarkEnabled:
          type: boolean
        ownerAdminId:
          type: string
    InvestMeResponse:
      type: object
      properties:
        email:
          type: string
        role:
          type: string
        capabilities:
          description: read | export | manage | sensitive
          type: array
          items:
            type: string
      required:
        - email
        - role
        - capabilities
    InvestCreateDeckBody:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
        notificationEmails:
          description: Recipients of IR notifications
          type: array
          items:
            type: string
      required:
        - title
    InvestVersionResponse:
      type: object
      properties:
        id:
          type: string
        deckId:
          type: string
        versionNumber:
          type: number
        versionName:
          type: object
        status:
          type: string
        pageCount:
          type: object
        processingError:
          type: object
        processingAttempts:
          type: number
        publishedAt:
          type: object
        createdAt:
          type: string
      required:
        - id
        - deckId
        - versionNumber
        - status
        - processingAttempts
        - createdAt
    InvestDeckResponse:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: object
        notificationEmails:
          type: array
          items:
            type: string
        currentVersionId:
          type: object
        archivedAt:
          type: object
        createdAt:
          type: string
        versions:
          type: array
          items:
            $ref: '#/components/schemas/InvestVersionResponse'
      required:
        - id
        - title
        - notificationEmails
        - createdAt
        - versions
    InvestUpdateDeckBody:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
        notificationEmails:
          type: array
          items:
            type: string
    InvestUploadIntentBody:
      type: object
      properties:
        versionName:
          type: string
          description: Display name for this version ("Series A v3")
    InvestUploadIntentResponse:
      type: object
      properties:
        versionId:
          type: string
        uploadUrl:
          type: string
          description: S3 presigned-POST URL (browser uploads directly)
        uploadFields:
          type: object
          description: Form fields that must accompany the POST
      required:
        - versionId
        - uploadUrl
        - uploadFields
    InvestFinalizeUploadBody:
      type: object
      properties:
        fileSha256:
          type: string
          description: sha256 of the uploaded file, if computed client-side
    InvestUpdatePageBody:
      type: object
      properties:
        label:
          type: object
          description: IR label, e.g. "Traction"
        isKeyPage:
          type: boolean
          description: Key pages feed the engagement score
    InvestViewerPageMeta:
      type: object
      properties:
        'n':
          type: number
        width:
          type: number
        height:
          type: number
      required:
        - 'n'
        - width
        - height
    InvestViewerManifestResponse:
      type: object
      properties:
        deckTitle:
          type: string
        pageCount:
          type: number
        pages:
          type: array
          items:
            $ref: '#/components/schemas/InvestViewerPageMeta'
        sessionRef:
          type: string
          description: Short session reference (also in the watermark)
        watermarked:
          type: boolean
          description: Whether pages carry a per-session watermark
      required:
        - deckTitle
        - pageCount
        - pages
        - sessionRef
        - watermarked
    InvestEventBody:
      type: object
      properties:
        eventId:
          type: string
          description: Client-generated UUID; the idempotency key
        tabId:
          type: string
          description: Per-tab UUID (sessionStorage-scoped)
        seq:
          type: number
          description: Monotonic per-tab sequence number
        type:
          type: string
          enum:
            - SESSION_START
            - PAGE_VIEW
            - HEARTBEAT
            - PAGE_EXIT
            - STATE_CHANGE
            - SESSION_END
        pageNumber:
          type: number
        detail:
          type: string
          enum:
            - hidden
            - visible
            - blurred
            - focused
            - idle
            - active
        clientTs:
          type: string
          description: Client wall clock (clamped to serverTs ± 5 min at ingest)
      required:
        - eventId
        - tabId
        - seq
        - type
        - clientTs
    InvestEventsBatchBody:
      type: object
      properties:
        events:
          description: Max 50 events per batch
          type: array
          items:
            $ref: '#/components/schemas/InvestEventBody'
      required:
        - events
    InvestEventsBatchResponse:
      type: object
      properties:
        accepted:
          type: number
        duplicates:
          type: number
          description: Duplicates the client can prune from its retry buffer
      required:
        - accepted
        - duplicates
    InvestCreateNoticeBody:
      type: object
      properties:
        bodyMarkdown:
          type: string
          description: Markdown body shown on the gate — editable without a deploy
        lawfulBasisMode:
          type: string
          enum:
            - consent
            - legitimate-interest
      required:
        - bodyMarkdown
    InvestSubjectBody:
      type: object
      properties:
        email:
          type: string
          description: The data subject email
      required:
        - email
    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 Permara 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
    FundingSelectionBody:
      type: object
      properties:
        asset:
          type: string
          enum:
            - USDC
            - USDT
        network:
          type: string
          enum:
            - tron
            - base
            - base-sepolia
      required:
        - asset
        - network
    EnvelopeMessageBody:
      type: object
      properties:
        paymentObjectId:
          type: string
          description: Client-minted 32-byte payment correlation id (0x + 64 hex).
        payer:
          type: string
          description: The payer's registered signer address (EOA or Safe).
        payeeDid:
          type: string
          example: did:web:id.safebank.ai:lance
        amountBaseUnits:
          type: string
          description: Authorized value as integer base units of `asset`.
        asset:
          type: string
          example: USD
          description: Asset/currency symbol of the authorized value.
        assetExponent:
          type: number
          description: >-
            Base-unit scale of amountBaseUnits (USD=2, USDC=6). Verified against
            the platform registry.
        nonce:
          type: string
          description: Single-use replay nonce (uint256, decimal string), scoped per payer.
        expiresAt:
          type: string
          description: Unix seconds expiry.
      required:
        - paymentObjectId
        - payer
        - payeeDid
        - amountBaseUnits
        - asset
        - assetExponent
        - nonce
        - expiresAt
    SubstitutionPermissionsBody:
      type: object
      properties:
        destination:
          type: boolean
          description: May settle to a different (consented, VERIFIED) destination.
        provider:
          type: boolean
          description: May use a different payout provider.
        rail:
          type: boolean
          description: May use a different rail class.
        network:
          type: boolean
          description: May settle over a different network/chain.
        settlementAsset:
          type: boolean
          description: May deliver a different settlement asset.
      required:
        - destination
        - provider
        - rail
        - network
        - settlementAsset
    MinRecipientProceedsBody:
      type: object
      properties:
        amountBaseUnits:
          type: string
          description: Floor in integer base units of `asset` at `exponent`.
        asset:
          type: string
          example: EUR
        exponent:
          type: number
          description: Base-unit scale of amountBaseUnits.
      required:
        - amountBaseUnits
        - asset
        - exponent
    EnvelopeDocBody:
      type: object
      properties:
        policySha256:
          type: string
          description: >-
            Content-address (sha256 of the canonical rules JSON) of the sender
            org's ACTIVE payment policy the payer authorized under. ENFORCED:
            intake rejects a mismatch (ENVELOPE_POLICY_MISMATCH) and the
            PRE_SUBMIT re-proof holds on a policy swap (POLICY_DRIFT).
        permittedSettlementAssets:
          description: Assets the recipient may be paid in; absent = any.
          type: array
          items:
            type: string
        permittedNetworks:
          description: Networks value may settle over; absent = any.
          type: array
          items:
            type: string
        permittedProviders:
          description: Payout providers permitted; absent = any.
          type: array
          items:
            type: string
        railExclusions:
          description: Rail classes the payer forbids.
          type: array
          items:
            type: string
        substitutionPermissions:
          description: Per-axis substitution consent — the primary form.
          allOf:
            - $ref: '#/components/schemas/SubstitutionPermissionsBody'
        allowRouteChange:
          type: boolean
          description: >-
            Legacy shorthand: true expands to every axis except settlementAsset.
            Rejected if it contradicts substitutionPermissions.
        maxFeeBaseUnits:
          type: string
          description: >-
            Payer fee ceiling in base units of the signed asset. "0" = TRUE
            zero-fee ceiling; ABSENT = platform default.
        minRecipientProceeds:
          description: >-
            Floor on recipient proceeds, enforced on every route in any
            currency.
          allOf:
            - $ref: '#/components/schemas/MinRecipientProceedsBody'
        maxAttempts:
          type: number
          description: Max substitution attempts permitted; absent = platform default.
        jurisdictionConstraints:
          description: ISO-3166 alpha-2 destination countries permitted; absent = any.
          type: array
          items:
            type: string
        memoHash:
          type: string
          description: >-
            sha256 (0x + 64 hex) of the plaintext memo; plaintext rides outside
            the signed envelope.
    PaymentEnvelopeBody:
      type: object
      properties:
        message:
          $ref: '#/components/schemas/EnvelopeMessageBody'
        doc:
          $ref: '#/components/schemas/EnvelopeDocBody'
        signature:
          type: string
          description: EIP-712 signature over the message + docHash.
      required:
        - message
        - doc
        - signature
    InviteBeneficiaryBody:
      type: object
      properties:
        email:
          type: string
          example: finance@recipient.example
          description: Recipient email. Required when no phone is given.
        phone:
          type: string
          example: '+15551234567'
          description: Recipient mobile number (E.164). Required when no email is given.
    CreateCorridorPaymentBody:
      type: object
      properties:
        phone:
          type: string
          example: '+15551234567'
          description: Recipient phone (E.164). Exactly one of phone | did.
        did:
          type: string
          example: did:safebank:lance
          description: Recipient DID (did:safebank / did:web form or bare handle).
        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. INTERNAL_BALANCE (default): from
            the primary account reported by GET /v1/balance — the balance is
            checked (402 INSUFFICIENT_FUNDS) and the money is deposited into
            escrow from that account (fundingInstructions come back; the payment
            waits in AWAITING_SOURCE_FUNDS). ONCHAIN_DEPOSIT: the same lifecycle
            from an address you name (refundAddress). SAFE_PROPOSAL is not
            available yet.
        refundAddress:
          type: string
          description: >-
            ONCHAIN_DEPOSIT (EVM) only: address an expired/canceled escrow claim
            refunds to. Required for that mode.
        funding:
          description: >-
            Funding asset/network; {asset:'USDT',network:'tron'} rides the
            partner path.
          allOf:
            - $ref: '#/components/schemas/FundingSelectionBody'
        envelope:
          description: The payer-signed Universal Payment Envelope (DID payments).
          allOf:
            - $ref: '#/components/schemas/PaymentEnvelopeBody'
        memo:
          type: string
          description: >-
            Plaintext memo — request metadata, never signed; the envelope binds
            only its memoHash. Stored under retention policy.
        requestedDestinationId:
          type: string
          description: >-
            One of the RECIPIENT's active destination ids (server-verified
            ownership).
        speedPreference:
          type: string
          enum:
            - FASTEST
            - CHEAPEST
        maximumFeeMinor:
          type: string
          example: '300'
          description: >-
            Fee ceiling in destination-currency minor units. Routes whose total
            fees exceed it are excluded with MAX_FEE_EXCEEDED, never silently
            offered.
        allowedRails:
          description: >-
            Rail allowlist by destination type (e.g. PAYPAL, VENMO,
            SAFEBANK_BALANCE). Absent = all rails.
          type: array
          items:
            type: string
        invite:
          description: >-
            Invite a beneficiary who is not on Permara yet (email or mobile
            number — at least one). Valid only with DID addressing of a
            reserved/unknown handle, and required there: Permara reserves the
            handle, emails or texts an onboarding link, and the payment parks as
            a pending claim until they verify and choose a payout.
          allOf:
            - $ref: '#/components/schemas/InviteBeneficiaryBody'
      required:
        - amountUsd
    FundingInstructionStepResponse:
      type: object
      properties:
        kind:
          type: string
          enum:
            - approve
            - createClaim
        to:
          type: string
        data:
          type: string
        value:
          type: string
      required:
        - kind
        - to
        - data
        - value
    FundingInstructionsResponse:
      type: object
      properties:
        provider:
          type: string
          enum:
            - onchain
            - mock
        escrowAddress:
          type: string
        dealId:
          type: string
        tokenAddress:
          type: string
        chainId:
          type: number
        amountMinor:
          type: string
          description: Remaining amount to protect (integer minor units).
        steps:
          type: array
          items:
            $ref: '#/components/schemas/FundingStepResponse'
        note:
          type: string
      required:
        - provider
        - amountMinor
    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) plus an `fx` leg-snapshot object.
        exchangeRate:
          type: string
        estimatedDeliverySeconds:
          type: object
        speedClass:
          type: string
          enum:
            - INSTANT_EXPECTED
            - SAME_DAY
            - NEXT_BUSINESS_DAY
            - STANDARD
            - UNKNOWN
          description: Normalized delivery-speed expectation.
        guaranteeLevel:
          type: string
          enum:
            - CONTRACTUAL_SLA
            - PROVIDER_ESTIMATE
            - HISTORICAL_ESTIMATE
            - NO_GUARANTEE
          description: >-
            What backs the speed class; CONTRACTUAL_SLA only with a real
            contract.
        rank:
          type: object
          description: 1-based scoring rank; selected destination first.
        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
        - speedClass
        - guaranteeLevel
        - 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
        recipientDid:
          type: string
          nullable: true
          description: Canonical recipient DID for DID-addressed payments.
        envelopeId:
          type: string
          nullable: true
          description: Bound payment-envelope id (payer-signed authorization).
        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
        fundingInstructions:
          description: Present for ONCHAIN_DEPOSIT payments awaiting the escrow deposit.
          allOf:
            - $ref: '#/components/schemas/FundingInstructionsResponse'
        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
        recipientDid:
          type: string
          nullable: true
          description: Canonical recipient DID for DID-addressed payments.
        envelopeId:
          type: string
          nullable: true
          description: Bound payment-envelope id (payer-signed authorization).
        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
        fundingInstructions:
          description: >-
            Present for ONCHAIN_DEPOSIT payments still awaiting/confirming the
            deposit — lets a refreshed funding screen re-render the deposit
            address.
          allOf:
            - $ref: '#/components/schemas/FundingInstructionsResponse'
      required:
        - paymentId
        - status
        - sourceChainKey
        - sourceAssetId
        - sourceAmountBaseUnits
        - sourceFundingMode
        - holdReasons
        - createdAt
    CorridorPaymentListResponse:
      type: object
      properties:
        payments:
          type: array
          items:
            $ref: '#/components/schemas/CorridorPaymentResponse'
      required:
        - payments
    ExcludedRouteResponse:
      type: object
      properties:
        destinationId:
          type: string
          description: Destination that was withheld.
        provider:
          type: string
        rail:
          type: string
          description: Destination rail type.
        reasonCode:
          type: string
          description: Why the route was withheld (eligibility or scoring reason code).
      required:
        - destinationId
        - reasonCode
    RouteQuoteListResponse:
      type: object
      properties:
        routes:
          type: array
          items:
            $ref: '#/components/schemas/RouteQuoteResponse'
        excludedRoutes:
          description: >-
            Routes withheld at the last quote pass, with reason codes — the
            sender learns why the fastest/cheapest rail was not offered instead
            of being silently rerouted.
          type: array
          items:
            $ref: '#/components/schemas/ExcludedRouteResponse'
      required:
        - routes
        - excludedRoutes
    SelectRouteBody:
      type: object
      properties:
        routeQuoteId:
          type: string
          description: Route quote id to select.
      required:
        - routeQuoteId
    SimulateFundingBody:
      type: object
      properties:
        amountMinor:
          type: string
          description: Deposit amount in integer minor units.
          example: '8000000'
      required:
        - amountMinor
    ReviewNoteBody:
      type: object
      properties: {}
    ReviewInfoBody:
      type: object
      properties: {}
    ApprovalSignatureResponse:
      type: object
      properties:
        memberId:
          type: string
        decision:
          type: string
          enum:
            - APPROVE
            - REJECT
        weightAtSigning:
          type: number
        invalidatedAt:
          type: object
        createdAt:
          type: string
      required:
        - memberId
        - decision
        - weightAtSigning
        - createdAt
    ApprovalRequestResponse:
      type: object
      properties:
        id:
          type: string
        paymentId:
          type: string
        requiredWeight:
          type: number
        approvalGroup:
          type: object
        status:
          type: string
          enum:
            - PENDING
            - APPROVED
            - REJECTED
            - EXPIRED
            - INVALIDATED
        receivedWeight:
          type: number
          description: Σ non-invalidated APPROVE weights so far.
        signatures:
          type: array
          items:
            $ref: '#/components/schemas/ApprovalSignatureResponse'
        createdAt:
          type: string
      required:
        - id
        - paymentId
        - requiredWeight
        - status
        - receivedWeight
        - signatures
        - createdAt
    ApprovalListResponse:
      type: object
      properties:
        approvals:
          type: array
          items:
            $ref: '#/components/schemas/ApprovalRequestResponse'
      required:
        - approvals
    SignApprovalBody:
      type: object
      properties:
        decision:
          type: string
          enum:
            - APPROVE
            - REJECT
          default: APPROVE
        signature:
          type: string
          description: >-
            EIP-191 signature over the approval vote message (optional
            hardening).
    CreateCorridorIntakeBody:
      type: object
      properties:
        contactName:
          type: string
        contactEmail:
          type: string
        contactPhone:
          type: string
        legalName:
          type: string
          description: Registered legal name of the sending entity
        tradingName:
          type: string
        legalForm:
          type: string
          description: e.g. GmbH | AG | UG | GMBH_CO_KG | KG_OHG | OTHER
        registrationNumber:
          type: string
          description: Commercial register / company number, e.g. "HRB 12345 B"
        registerCourt:
          type: string
          description: >-
            Register court (Amtsgericht) — German register numbers are only
            unique per court
        representativeRole:
          type: string
          description: Role of the contact, e.g. "Geschäftsführer", "Prokurist", "CFO"
        incorporationDate:
          type: string
          description: Incorporation date, ISO YYYY-MM-DD
        vatId:
          type: string
          description: VAT id / USt-IdNr
        registrationCountry:
          type: string
          description: ISO-3166 alpha-2
        registeredAddress:
          type: string
        website:
          type: string
        sourceAsset:
          type: string
          enum:
            - USDT
            - USDC
        sourceNetwork:
          type: string
          enum:
            - tron
            - ethereum
            - base
        originatingWalletAddress:
          type: string
          description: >-
            Originating wallet, verbatim (TRON base58check is case-sensitive).
            Screening input only — never a deposit address.
        originatingWalletCustody:
          type: string
          enum:
            - SELF_HOSTED
            - EXCHANGE_HOSTED
            - OTHER_THIRD_PARTY
          description: Who controls the originating wallet (Travel Rule)
        destinationCurrency:
          type: string
          enum:
            - EUR
        destinationRail:
          type: string
          enum:
            - SEPA
            - SEPA_INSTANT
        expectedMonthlyVolume:
          type: string
          enum:
            - UNDER_10K
            - 10K_50K
            - 50K_250K
            - 250K_1M
            - OVER_1M
        expectedFrequency:
          type: string
          enum:
            - ONE_TIME
            - MONTHLY
            - WEEKLY
            - DAILY
        firstTransactionAmount:
          type: string
          description: Legacy free text, e.g. "100000 USDT"
        firstTransactionAmountValue:
          type: string
          description: First transaction amount as a decimal string
        firstTransactionAmountAsset:
          type: string
          enum:
            - USDT
            - USDC
            - EUR
        purposeDescription:
          type: string
        underlyingGoodsServices:
          type: string
        endUserOrDeliveryDestination:
          type: string
        sourceOfFundsCategory:
          type: string
          enum:
            - OPERATING_REVENUE
            - INVESTMENT_CAPITAL
            - LOAN_FACILITY
            - ASSET_SALE
            - CRYPTO_TRADING
            - TREASURY_RESERVES
            - OTHER
          description: >-
            Category only — source-of-funds EVIDENCE goes to the banking partner
            (Tier 2)
        sourceOfFundsNote:
          type: string
          description: Required when sourceOfFundsCategory is OTHER
        beneficiaryLegalName:
          type: string
        beneficiaryIban:
          type: string
          description: IBAN — spaces are stripped server-side
        beneficiaryBic:
          type: string
        beneficiaryBankName:
          type: string
        beneficiaryBankCountry:
          type: string
          description: ISO-3166 alpha-2
        beneficiaryAddress:
          type: string
          description: Beneficiary's own address (Travel Rule)
        beneficiaryCountry:
          type: string
          description: Beneficiary's country of residence/incorporation, ISO-3166 alpha-2
        beneficiarySameLegalEntity:
          type: boolean
          description: Is the beneficiary the same legal entity as the sender?
        relationshipDescription:
          type: string
          description: Required when the beneficiary is a different legal entity
        canDocumentRelationship:
          type: boolean
          description: The sender attests the relationship can be documented on request
        attestAccuracy:
          type: boolean
        declinedByOtherProvider:
          type: boolean
          description: 'Disclosure: has any other provider declined this corridor?'
        declinedByOtherProviderNotes:
          type: string
        ackTier2ToPartner:
          type: boolean
          description: Identity/UBO/source-of-funds docs go to the banking partner
        ackNoFundsBeforeConfirmation:
          type: boolean
          description: No funds move until a deposit address is confirmed in writing
      required:
        - contactName
        - contactEmail
        - legalName
        - legalForm
        - registrationNumber
        - representativeRole
        - registrationCountry
        - registeredAddress
        - sourceAsset
        - sourceNetwork
        - originatingWalletCustody
        - destinationCurrency
        - destinationRail
        - expectedMonthlyVolume
        - expectedFrequency
        - purposeDescription
        - sourceOfFundsCategory
        - beneficiaryLegalName
        - beneficiaryIban
        - beneficiaryAddress
        - beneficiaryCountry
        - beneficiarySameLegalEntity
        - canDocumentRelationship
        - attestAccuracy
        - declinedByOtherProvider
        - ackTier2ToPartner
        - ackNoFundsBeforeConfirmation
    CorridorIntakeActionBody:
      type: object
      properties:
        note:
          type: string
          description: Written recommendation / reason — required on every transition
      required:
        - note
    StartKycBody:
      type: object
      properties:
        legalName:
          type: string
          description: >-
            Legal name as it appears on the ID being verified. Persisted to the
            member set-if-null; required only when the account has no display
            name.
    AdvanceVerificationBody:
      type: object
      properties:
        target:
          type: string
          enum:
            - KYC
            - KYB
          description: Session to advance (default KYC). KYB is never auto-driven by KYC.
    OnboardingAccountBody:
      type: object
      properties:
        termsVersion:
          type: string
          description: >-
            Terms-of-service document version being accepted (e.g. "2026-08").
            Recorded set-if-null: a re-login never overwrites the original
            acceptance.
    InvitePreviewResponse:
      type: object
      properties:
        tenantName:
          type: string
          example: Northstar Supply Demo
        invitedBy:
          type: string
          nullable: true
          example: Maya Torres
          description: Display name or email of the inviter, when known.
        inviteeEmail:
          type: string
          nullable: true
          example: d•••@northstar-demo.test
          description: Invitee email, masked.
        status:
          type: string
          enum:
            - PENDING
            - ACCEPTED
            - EXPIRED
            - REVOKED
      required:
        - tenantName
        - invitedBy
        - inviteeEmail
        - status
    AcceptInviteBody:
      type: object
      properties:
        token:
          type: string
          description: Invite token from the member invitation.
        displayName:
          type: string
          description: Display name to record on the member.
      required:
        - token
    CreateBusinessProfileBody:
      type: object
      properties:
        contactName:
          type: string
        contactEmail:
          type: string
        contactPhone:
          type: string
        representativeRole:
          type: string
          description: Role of the contact, e.g. "Geschäftsführer", "Prokurist", "CFO"
        legalName:
          type: string
          description: Registered legal name of the sending entity
        tradingName:
          type: string
        legalForm:
          type: string
          description: e.g. GmbH | AG | UG | GMBH_CO_KG | KG_OHG | OTHER
        registrationNumber:
          type: string
          description: Commercial register / company number, e.g. "HRB 12345 B"
        registerCourt:
          type: string
          description: >-
            Register court (Amtsgericht) — German register numbers are only
            unique per court
        registrationCountry:
          type: string
          description: ISO-3166 alpha-2
        registeredAddress:
          type: string
        vatId:
          type: string
          description: VAT id / USt-IdNr
        incorporationDate:
          type: string
          description: Incorporation date, ISO YYYY-MM-DD
        website:
          type: string
        purposeDescription:
          type: string
        expectedMonthlyVolume:
          type: string
          enum:
            - UNDER_10K
            - 10K_50K
            - 50K_250K
            - 250K_1M
            - OVER_1M
        expectedFrequency:
          type: string
          enum:
            - ONE_TIME
            - MONTHLY
            - WEEKLY
            - DAILY
        firstTransactionAmountValue:
          type: string
          description: First transaction amount as a decimal string
        firstTransactionAmountAsset:
          type: string
          enum:
            - USDT
            - USDC
            - EUR
        originatingWalletAddress:
          type: string
          description: >-
            Originating wallet, verbatim (TRON base58check is case-sensitive).
            Screening input only — never a deposit address.
        originatingWalletCustody:
          type: string
          enum:
            - SELF_HOSTED
            - EXCHANGE_HOSTED
            - OTHER_THIRD_PARTY
          description: Who controls the originating wallet (Travel Rule)
        sourceOfFundsCategory:
          type: string
          enum:
            - OPERATING_REVENUE
            - INVESTMENT_CAPITAL
            - LOAN_FACILITY
            - ASSET_SALE
            - CRYPTO_TRADING
            - TREASURY_RESERVES
            - OTHER
          description: >-
            Category only — source-of-funds EVIDENCE goes to the banking partner
            (Tier 2)
        sourceOfFundsNote:
          type: string
          description: Required when sourceOfFundsCategory is OTHER
        underlyingGoodsServices:
          type: string
        attestAccuracy:
          type: boolean
        declinedByOtherProvider:
          type: boolean
          description: 'Disclosure: has any other provider declined this corridor?'
        declinedByOtherProviderNotes:
          type: string
        ackTier2ToPartner:
          type: boolean
          description: Identity/UBO/source-of-funds docs go to the banking partner
        ackNoFundsBeforeConfirmation:
          type: boolean
          description: No funds move until a deposit address is confirmed in writing
      required:
        - contactName
        - contactEmail
        - representativeRole
        - legalName
        - legalForm
        - registrationNumber
        - registrationCountry
        - registeredAddress
        - purposeDescription
        - expectedMonthlyVolume
        - attestAccuracy
        - declinedByOtherProvider
        - ackTier2ToPartner
        - ackNoFundsBeforeConfirmation
    AddOwnershipNodeBody:
      type: object
      properties:
        kind:
          type: string
          enum:
            - PERSON
            - ENTITY
        name:
          type: string
        jurisdiction:
          type: string
          description: ISO-3166 alpha-2
        registrationNumber:
          type: string
          description: ENTITY only
        dateOfBirth:
          type: string
          description: PERSON, YYYY-MM-DD (screening input)
        memberId:
          type: string
          description: Platform member id when the person is a member
      required:
        - kind
        - name
    AddOwnershipEdgeBody:
      type: object
      properties:
        parentNodeId:
          type: string
          description: The owning node
        childNodeId:
          type: string
          description: The owned entity; defaults to the applicant root
        percent:
          type: string
          description: Percent 0-100, up to 4 decimal places ("25", "33.3333")
        kind:
          type: string
          enum:
            - SHAREHOLDING
            - VOTING
            - CONTROL
      required:
        - parentNodeId
        - percent
        - kind
    CreateKybDocumentBody:
      type: object
      properties: {}
    ResolveScreeningBody:
      type: object
      properties: {}
    UnifiedRecipientBody:
      type: object
      properties:
        phoneNumber:
          type: string
          example: '+12125550123'
          description: E.164 phone.
        handle:
          type: string
          example: '@acme-ops'
        email:
          type: string
        identityId:
          type: string
          description: A canonical Permara identity id.
        did:
          type: string
          example: did:safebank:lance
          description: Recipient DID (did:safebank / did:web form or bare handle).
        walletAddress:
          type: string
          description: Raw wallet — refused with guidance (legacy shape).
    UnifiedAmountBody:
      type: object
      properties:
        value:
          type: string
          example: '250.00'
          description: Decimal amount (≤6 fraction digits).
        currency:
          type: string
          example: USD
      required:
        - value
        - currency
    UnifiedFundingBody:
      type: object
      properties:
        asset:
          type: string
          example: USDC
          description: Funding asset symbol.
        chain:
          type: string
          example: base
          description: Funding chain key or id.
        chainId:
          type: number
          description: EVM chain id override (default 84532).
        network:
          type: string
          enum:
            - tron
            - base
            - base-sepolia
          description: >-
            Funding network for asset selection; {asset:'USDT', network:'tron'}
            rides the partner deposit path. Requires asset.
        mode:
          type: string
          enum:
            - INTERNAL_BALANCE
            - ONCHAIN_DEPOSIT
            - SAFE_PROPOSAL
          description: >-
            How the sender funds the payment. INTERNAL_BALANCE (default): from
            the primary account reported by GET /v1/balance — the balance is
            checked (402 INSUFFICIENT_FUNDS) and the money is deposited into
            escrow from that account (fundingInstructions come back; the payment
            waits in FUNDS_PENDING). ONCHAIN_DEPOSIT: the same lifecycle from an
            address you name (refundAddress). SAFE_PROPOSAL is not available
            yet.
        refundAddress:
          type: string
          description: >-
            ONCHAIN_DEPOSIT (EVM) only: address an expired escrow claim refunds
            to.
    UnifiedDeliveryBody:
      type: object
      properties:
        preference:
          type: string
          enum:
            - instant
            - fastest
            - cheapest
            - standard
        maximumFeeMinor:
          type: string
          description: Fee ceiling, destination minor units (digits).
        allowedRails:
          description: Rail allowlist by destination type.
          type: array
          items:
            type: string
    UnifiedPurposeBody:
      type: object
      properties:
        type:
          type: string
          example: invoice
        invoiceId:
          type: string
        memo:
          type: string
    CreateUnifiedPaymentBody:
      type: object
      properties:
        recipient:
          $ref: '#/components/schemas/UnifiedRecipientBody'
        amount:
          $ref: '#/components/schemas/UnifiedAmountBody'
        funding:
          $ref: '#/components/schemas/UnifiedFundingBody'
        delivery:
          $ref: '#/components/schemas/UnifiedDeliveryBody'
        purpose:
          $ref: '#/components/schemas/UnifiedPurposeBody'
        envelope:
          description: >-
            The payer-signed Universal Payment Envelope. Valid only with a DID
            recipient — same semantics as the off-ramp path.
          allOf:
            - $ref: '#/components/schemas/PaymentEnvelopeBody'
        memo:
          type: string
          description: >-
            Plaintext memo — request metadata, never signed; the envelope binds
            only its memoHash. Stored under retention policy.
        metadata:
          type: object
          description: Echoed back verbatim; never interpreted.
        idempotencyKey:
          type: string
          description: Tenant-scoped replay key (or Idempotency-Key header).
        requestedDestinationId:
          type: string
          description: >-
            One of the recipient's active destination ids (server-verified
            ownership).
      required:
        - recipient
        - amount
    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
    UnifiedPaymentResponse:
      type: object
      properties:
        paymentId:
          type: string
        status:
          type: string
          enum:
            - CREATED
            - IDENTITY_RESOLUTION
            - CREDENTIAL_CHECK
            - CREDENTIAL_HOLD
            - POLICY_CHECK
            - APPROVAL_REQUIRED
            - APPROVAL_PENDING
            - APPROVED
            - ROUTE_QUOTED
            - AWAITING_CONFIRMATION
            - FUNDS_PENDING
            - FUNDS_CONFIRMED
            - ROUTING
            - PROVIDER_SUBMITTED
            - PROVIDER_PROCESSING
            - ACTION_REQUIRED
            - DELIVERED
            - FAILED
            - REFUND_PENDING
            - REFUNDED
            - CANCELED
          description: Normalized (spec §14) status.
        internalStatus:
          type: string
          description: The internal corridor status (diagnostic).
        recipientKind:
          type: string
          description: PHONE | HANDLE | EMAIL | IDENTITY | DID.
        recipientDisplay:
          type: object
        envelopeId:
          type: object
          description: Bound payment-envelope id (payer-signed authorization).
        recipientIdentityId:
          type: object
        amountValue:
          type: string
        amountCurrency:
          type: string
        fundingAssetId:
          type: string
        fundingChainKey:
          type: string
        holdReasons:
          type: array
          items:
            type: string
        approvalRequestId:
          type: object
        selectedRouteQuoteId:
          type: object
        purpose:
          type: object
        metadata:
          type: object
        failureReason:
          type: object
        deliveredAt:
          type: object
        createdAt:
          type: string
      required:
        - paymentId
        - status
        - internalStatus
        - recipientKind
        - amountValue
        - amountCurrency
        - fundingAssetId
        - fundingChainKey
        - holdReasons
        - createdAt
    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
    ExecutePaymentBody:
      type: object
      properties:
        ownerAddress:
          type: string
          description: Owner address to act as. Optional for single-owner wallets.
    SignPaymentBody:
      type: object
      properties:
        signature:
          type: string
          description: Raw secp256k1 signature over the safeTxHash.
      required:
        - signature
    Function:
      type: object
      properties: {}
    QuotePayeeResponse:
      type: object
      properties:
        did:
          type: string
        verified:
          type: boolean
      required:
        - did
        - verified
    QuoteSettlementResponse:
      type: object
      properties:
        currency:
          type: string
        railType:
          type: string
          description: Rail class of the settlement destination
      required:
        - currency
        - railType
    QuoteFundingAmountResponse:
      type: object
      properties:
        value:
          type: string
          description: Amount in the funding asset, decimal string
        asset:
          type: string
        chain:
          type: string
      required:
        - value
        - asset
        - chain
    QuoteSenderResponse:
      type: object
      properties:
        requiredAmount:
          $ref: '#/components/schemas/QuoteFundingAmountResponse'
        maxCostUsd:
          type: string
          description: USD valuation of the required funding, decimal string
      required:
        - requiredAmount
        - maxCostUsd
    QuoteMoneyResponse:
      type: object
      properties:
        value:
          type: string
          description: Decimal amount string
        currency:
          type: string
      required:
        - value
        - currency
    QuoteRecipientResponse:
      type: object
      properties:
        guaranteedAmount:
          $ref: '#/components/schemas/QuoteMoneyResponse'
      required:
        - guaranteedAmount
    QuoteFeesResponse:
      type: object
      properties:
        total:
          $ref: '#/components/schemas/QuoteMoneyResponse'
        safebank:
          $ref: '#/components/schemas/QuoteMoneyResponse'
      required:
        - total
        - safebank
    PaymentQuoteResponse:
      type: object
      properties:
        quoteId:
          type: string
        payee:
          $ref: '#/components/schemas/QuotePayeeResponse'
        recipientSettlement:
          $ref: '#/components/schemas/QuoteSettlementResponse'
        sender:
          $ref: '#/components/schemas/QuoteSenderResponse'
        recipient:
          $ref: '#/components/schemas/QuoteRecipientResponse'
        fees:
          $ref: '#/components/schemas/QuoteFeesResponse'
        estimatedSettlementSeconds:
          type: number
          nullable: true
        expiresAt:
          type: string
      required:
        - quoteId
        - payee
        - recipientSettlement
        - sender
        - recipient
        - fees
        - estimatedSettlementSeconds
        - expiresAt
    CreateWebhookEndpointBody:
      type: object
      properties:
        url:
          type: string
          example: https://example.com/webhooks/permara
        enabledEvents:
          example:
            - payment.*
          type: array
          items:
            type: string
        description:
          type: string
      required:
        - url
        - enabledEvents
    CreatedWebhookEndpointResponse:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
        enabledEvents:
          type: array
          items:
            type: string
        status:
          type: string
        description:
          type: object
        createdAt:
          type: string
        secret:
          type: string
          description: HMAC signing secret — shown ONCE, store it now.
      required:
        - id
        - url
        - enabledEvents
        - status
        - createdAt
        - secret
    UpdateWebhookEndpointBody:
      type: object
      properties:
        status:
          type: string
          enum:
            - ACTIVE
            - DISABLED
        enabledEvents:
          type: array
          items:
            type: string
    WebhookEndpointResponse:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
        enabledEvents:
          type: array
          items:
            type: string
        status:
          type: string
        description:
          type: object
        createdAt:
          type: string
      required:
        - id
        - url
        - enabledEvents
        - status
        - createdAt
    WebhookEndpointListResponse:
      type: object
      properties:
        endpoints:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEndpointResponse'
      required:
        - endpoints
    WebhookEventResponse:
      type: object
      properties:
        eventId:
          type: string
        eventType:
          type: string
        eventVersion:
          type: number
        resourceType:
          type: string
        resourceId:
          type: string
        createdAt:
          type: string
      required:
        - eventId
        - eventType
        - eventVersion
        - resourceType
        - resourceId
        - createdAt
    WebhookEventListResponse:
      type: object
      properties:
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventResponse'
      required:
        - events
    WebhookDeliveryResponse:
      type: object
      properties:
        id:
          type: string
        eventId:
          type: string
        endpointId:
          type: string
        status:
          type: string
        attempts:
          type: number
        httpStatus:
          type: object
        lastError:
          type: object
        nextAttemptAt:
          type: string
        deliveredAt:
          type: object
      required:
        - id
        - eventId
        - endpointId
        - status
        - attempts
        - nextAttemptAt
    WebhookDeliveryListResponse:
      type: object
      properties:
        deliveries:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDeliveryResponse'
      required:
        - deliveries
    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
    ReserveDidBody:
      type: object
      properties:
        email:
          type: string
          example: ap@harborgoods.example
          description: Email the invite goes to. One of email / phone is required.
        phone:
          type: string
          example: '+14155550123'
          description: >-
            Mobile number (E.164) the invite goes to. One of email / phone is
            required.
    DidAliasResponse:
      type: object
      properties:
        did:
          type: string
          example: >-
            did:permara:7f5df4d16e3c8a2e4b1c9d0a3f6e8b7c2d5a4f1e9c8b7a6d5e4f3c2b1a0f9e8d
        status:
          type: string
          enum:
            - PROVISIONED
            - RESERVED
            - ACTIVE
            - SUSPENDED
            - RELEASED
        subjectType:
          type: string
          enum:
            - TENANT
            - TENANT_MEMBER
        verifiedAt:
          type: string
          nullable: true
        createdAt:
          type: string
        version:
          type: number
          description: On-chain document version; 0 until the first record lands.
        registryStatus:
          type: string
          nullable: true
          description: >-
            PENDING | ANCHORED | FAILED | DEACTIVATED; null before the first
            attempt.
        registryTxHash:
          type: string
          nullable: true
        documentUri:
          type: string
          nullable: true
      required:
        - did
        - status
        - subjectType
        - createdAt
        - version
    DidAliasListResponse:
      type: object
      properties:
        dids:
          type: array
          items:
            $ref: '#/components/schemas/DidAliasResponse'
      required:
        - dids
    ResolveDidResponse:
      type: object
      properties:
        did:
          type: string
          example: >-
            did:permara:7f5df4d16e3c8a2e4b1c9d0a3f6e8b7c2d5a4f1e9c8b7a6d5e4f3c2b1a0f9e8d
          description: The canonical did:permara identifier.
        resolvable:
          type: boolean
          description: true when the DID resolves to a payable identity.
        displayName:
          type: string
          nullable: true
          description: The verified name the DID resolves to; null when not payable.
      required:
        - did
        - resolvable
    DidResolutionResultResponse:
      type: object
      properties:
        didResolutionMetadata:
          type: object
        didDocument:
          type: object
          nullable: true
        didDocumentMetadata:
          type: object
      required:
        - didResolutionMetadata
        - didDocumentMetadata
    PaymentProfileDidResponse:
      type: object
      properties:
        did:
          type: string
        status:
          type: string
        subjectType:
          type: string
      required:
        - did
        - status
        - subjectType
    PaymentProfileCredentialResponse:
      type: object
      properties:
        kind:
          type: string
        status:
          type: string
        expiresAt:
          type: string
          nullable: true
      required:
        - kind
        - status
    PaymentProfileDestinationResponse:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
        status:
          type: string
        ownershipStatus:
          type: string
        managed:
          type: boolean
          description: Permara-managed endpoint (auto-provisioned wallet).
        asset:
          type: string
          nullable: true
        network:
          type: string
          nullable: true
        currency:
          type: string
          nullable: true
        country:
          type: string
          nullable: true
        provider:
          type: string
          nullable: true
        maskedLabel:
          type: string
        priority:
          type: number
        isDefault:
          type: boolean
        allowAutomaticFallback:
          type: boolean
      required:
        - id
        - type
        - status
        - ownershipStatus
        - managed
        - maskedLabel
        - priority
        - isDefault
        - allowAutomaticFallback
    PaymentProfileResponse:
      type: object
      properties:
        tenantId:
          type: string
        dids:
          type: array
          items:
            $ref: '#/components/schemas/PaymentProfileDidResponse'
        credentials:
          type: array
          items:
            $ref: '#/components/schemas/PaymentProfileCredentialResponse'
        destinations:
          type: array
          items:
            $ref: '#/components/schemas/PaymentProfileDestinationResponse'
      required:
        - tenantId
        - dids
        - credentials
        - destinations
    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: Permara 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
        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
        - createdAt
        - chainId
    AttestationDomain:
      type: object
      properties:
        name:
          type: string
          example: Permara
        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: Permara 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 Permara
            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: Permara 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
    SettlementTokenCapability:
      type: object
      properties:
        symbol:
          type: string
          example: DUSD
          enum:
            - DUSD
            - USDC
        label:
          type: string
          example: Demo USD
          enum:
            - Demo USD
            - USDC
          description: The customer-facing name of the money on this chain.
        address:
          type: string
          example: '0xDdE94493605c1e64D9D5EE48A9CDA3A86bb4FfC9'
      required:
        - symbol
        - label
        - address
    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: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
          description: >-
            Canonical USDC token address on this chain, when USDC is the money
            there (mainnets). Testnets settle in Demo USD instead; see
            settlementToken.
        settlementToken:
          description: >-
            The money on this chain: Demo USD (DUSD) on a testnet, USDC on a
            mainnet.
          allOf:
            - $ref: '#/components/schemas/SettlementTokenCapability'
      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 person-verification (KYC) provider key (mock = sandbox).
        kybProvider:
          type: string
          example: mock
          description: Active business-verification (KYB) provider key (mock = sandbox).
        live:
          type: boolean
          example: false
          description: >-
            True only when a real person-verification vendor is configured; mock
            is sandbox-only.
        kybLive:
          type: boolean
          example: false
          description: >-
            True only when a real business-verification vendor is configured;
            mock is sandbox-only.
        enforcement:
          type: string
          enum:
            - 'off'
            - log
            - enforce
          description: Payment-path credential gating rollout stage.
        signedCredentials:
          type: boolean
          example: false
          description: >-
            True when issuance signs EIP-712 credential artifacts (platform
            issuer key + credential seal key configured); false means unsigned
            rows (recorded, honest).
        presentations:
          type: boolean
          example: false
          description: >-
            Wallet-signed credential presentations (challenge/verify). Requires
            signed credentials — unsigned rows have no hash to present.
        authorityEnforcement:
          type: string
          enum:
            - 'off'
            - log
            - enforce
          description: >-
            Member/authority gating rollout stage (independent of KYB
            enforcement).
      required:
        - sessions
        - provider
        - kybProvider
        - live
        - kybLive
        - enforcement
        - signedCredentials
        - presentations
        - authorityEnforcement
    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
    FundingCapability:
      type: object
      properties:
        mode:
          type: string
          example: FAUCET
          enum:
            - FAUCET
            - ONRAMP
          description: >-
            How money gets into an account on this deployment: the Demo USD
            faucet on a testnet, an on-ramp on a mainnet.
        onrampProvider:
          type: object
          example: null
          nullable: true
          description: The wired on-ramp provider, or null while none is.
      required:
        - mode
        - onrampProvider
    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: true
          description: >-
            Normalized payment intents on POST /v1/payments: recipient union
            (phone/handle/email/identity), delivery preferences, route quotes
            with speed classes, approvals, idempotent replay. Legacy Safe-debit
            bodies still work (deprecated).
        simulate:
          type: boolean
          example: false
          description: Dry-run transaction simulation — not shipped in this branch.
        autoramps:
          type: boolean
          example: false
          description: >-
            Sandbox ramps behind the RampProvider seam (simulated fiat leg). As
            available as the faucet.
        faucet:
          type: boolean
          example: true
          description: >-
            Demo USD from the faucet: minted on a configured testnet when the
            platform holds a faucet key.
        funding:
          $ref: '#/components/schemas/FundingCapability'
        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).
        did:
          type: boolean
          example: true
          description: >-
            Handle-addressed payments: resolve a payment handle and pay it
            directly.
        envelopes:
          type: boolean
          example: true
          description: >-
            Signed payment authorizations: payer-signed constraints checked
            before value release.
        webhooks:
          type: boolean
          example: false
          description: >-
            Outbound webhooks (endpoints, HMAC-signed deliveries, replay).
            Truth-computed off WEBHOOK_SECRET_KEY — without it endpoint secrets
            cannot be sealed.
        agreements:
          type: boolean
          example: false
          description: >-
            Verified Payment Agreements (ADR 0024): true iff
            AGREEMENTS_ENABLED=true.
        agreementsAi:
          type: object
          properties:
            enabled:
              type: boolean
            provider:
              type: string
              nullable: true
              enum:
                - anthropic
                - openai
                - deepseek
                - mock
            writes:
              type: boolean
          description: >-
            The LLM-first agreement chat runtime (ADR 0026): enabled iff
            agreements + AGREEMENTS_AI_ENABLED; provider is the configured
            adapter (mock = deterministic sandbox); writes reflects the
            AGREEMENTS_AI_WRITES_ENABLED kill switch.
        lending:
          type: object
          properties:
            enabled:
              type: boolean
            provider:
              type: string
              enum:
                - mock
                - onchain
            chainId:
              type: integer
              nullable: true
          description: >-
            Credential-gated lending pools (ADR 0027): enabled iff
            LENDING_ENABLED=true (every lending route 404s otherwise); provider
            is the configured twin (mock = in-process, onchain = the chain is
            the monetary authority); chainId is the pinned LENDING_CHAIN_ID
            (84532 on production), null when unpinned. Test funds only — Demo
            USD on a testnet.
      required:
        - identity
        - wallets
        - payments
        - cards
        - intents
        - simulate
        - autoramps
        - faucet
        - funding
        - treasury
        - agents
        - invoices
        - offramp
        - claims
        - destinationRails
        - did
        - envelopes
        - webhooks
        - agreements
        - agreementsAi
        - lending
    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
              - agreements:read
              - agreements:write
              - agreements:propose
              - ramps:read
              - ramps:write
              - lending:read
              - lending:write
              - keys:manage
              - members:read
              - members:manage
              - contacts:read
              - contacts:write
              - identity:read
              - identity:write
              - status:read
              - twofactor:read
              - twofactor:manage
              - webhooks:manage
              - '*'
        expiresAt:
          type: string
          description: Optional ISO-8601 expiry. A key past its expiry fails auth.
          example: '2027-01-01T00:00:00.000Z'
        rateLimitPerMinute:
          type: number
          description: Per-key rate limit (requests/minute) overriding route defaults.
          minimum: 1
          maximum: 10000
        ipAllowlist:
          description: >-
            Source-IP allowlist (exact IPs and IPv4 CIDRs). Non-empty fails
            CLOSED for other addresses.
          example:
            - 203.0.113.9
            - 10.1.0.0/16
          type: array
          items:
            type: string
        actingMemberId:
          type: string
          description: >-
            "Acting-as" TenantMember id. Payments created with this key are
            attributed to that member and run the full member/authority gate
            (limits, windows, approvals). Omit for a tenant-scoped key (KYB gate
            only).
      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: pm_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
    SetupSigningBody:
      type: object
      properties:
        required:
          type: boolean
          description: Require signatures on @HighRiskOperation routes (default true).
          default: true
    SigningSecretResponse:
      type: object
      properties:
        keyId:
          type: string
        signingSecret:
          type: string
          description: HMAC signing secret — shown ONCE, store it now.
        signingRequired:
          type: boolean
      required:
        - keyId
        - signingSecret
        - signingRequired
    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
              - agreements:read
              - agreements:write
              - agreements:propose
              - ramps:read
              - ramps:write
              - lending:read
              - lending: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
    BalanceAssetDto:
      type: object
      properties:
        symbol:
          type: string
          example: DUSD
          enum:
            - DUSD
            - USDC
        label:
          type: string
          example: Demo USD
          enum:
            - Demo USD
            - USDC
        address:
          type: string
          example: '0xDdE94493605c1e64D9D5EE48A9CDA3A86bb4FfC9'
        decimals:
          type: number
          example: 6
      required:
        - symbol
        - label
        - address
        - decimals
    BalanceAccountDto:
      type: object
      properties:
        kind:
          type: string
          enum:
            - SMART_ACCOUNT
            - ORG_MULTISIG
          description: The member's own smart account, or the organization's Safe.
        address:
          type: object
          nullable: true
          description: Null until the account exists.
        walletId:
          type: object
          nullable: true
          description: ORG_MULTISIG only.
        status:
          type: string
          example: READY
          description: >-
            SMART_ACCOUNT: READY | NOT_LINKED. ORG_MULTISIG: the Safe deployment
            status, or NONE.
        amountBaseUnits:
          type: object
          nullable: true
          description: Six-decimal base units; null when unreadable.
        amountUsd:
          type: object
          nullable: true
          example: '250.000000'
      required:
        - kind
        - address
        - status
        - amountBaseUnits
        - amountUsd
    BalanceResponse:
      type: object
      properties:
        chainId:
          type: number
          example: 84532
        chainName:
          type: string
          example: Base Sepolia
        testnet:
          type: boolean
          example: true
        asset:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/BalanceAssetDto'
        primary:
          type: string
          enum:
            - SMART_ACCOUNT
            - ORG_MULTISIG
        accounts:
          type: array
          items:
            $ref: '#/components/schemas/BalanceAccountDto'
        funding:
          type: string
          enum:
            - FAUCET
            - ONRAMP
          description: >-
            How money gets in here: the Demo USD faucet on a testnet, an on-ramp
            on a mainnet.
        partial:
          type: boolean
          description: True when a balance could not be read (never counted as zero).
        observedAt:
          type: string
          example: '2026-09-16T12:00:00.000Z'
      required:
        - chainId
        - chainName
        - testnet
        - asset
        - primary
        - accounts
        - funding
        - partial
        - observedAt
    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: object
          example: w1
          nullable: true
          description: Null for a smart-account drip.
        account:
          type: string
          nullable: true
          enum:
            - SMART_ACCOUNT
            - ORG_MULTISIG
        toAddress:
          type: string
          example: 0x…
          description: The funded address.
        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
        - account
        - toAddress
        - asset
        - amount
        - chainId
        - status
        - txHash
        - failureReason
        - createdAt
    FundAccountBody:
      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'
        account:
          type: string
          description: >-
            Which account to fund: the member's smart account or the
            organization's Safe. Defaults to the primary account from GET
            /v1/balance.
          enum:
            - SMART_ACCOUNT
            - ORG_MULTISIG
    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
    VerificationClientHandoffResponse:
      type: object
      properties:
        provider:
          type: string
          example: persona
          description: Provider key whose embedded client resumes this session.
        sessionRef:
          type: string
          description: The vendor's session reference (e.g. an inquiry id).
        token:
          type: string
          description: Short-lived client token for the embedded flow.
        templateId:
          type: string
        environmentId:
          type: string
      required:
        - provider
        - sessionRef
        - token
    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.
        client:
          nullable: true
          description: >-
            How the app resumes this session in the vendor's embedded client;
            null once the session is terminal or when the vendor only offers a
            hosted flow.
          type: object
          allOf:
            - $ref: '#/components/schemas/VerificationClientHandoffResponse'
        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
    IdentityResponse:
      type: object
      properties:
        id:
          type: string
        did:
          type: string
          example: did:safebank:cabc…
        identityType:
          type: string
          enum:
            - PERSON
            - ORGANIZATION
        status:
          type: string
          enum:
            - ACTIVE
            - SUSPENDED
            - DEACTIVATED
        subjectType:
          type: string
          enum:
            - TENANT
            - TENANT_MEMBER
        subjectId:
          type: string
        primarySmartAccountId:
          type: object
        credentialAssuranceLevel:
          type: object
        createdAt:
          type: string
      required:
        - id
        - did
        - identityType
        - status
        - subjectType
        - subjectId
        - createdAt
    IdentityListResponse:
      type: object
      properties:
        identities:
          type: array
          items:
            $ref: '#/components/schemas/IdentityResponse'
      required:
        - identities
    CreateIdentityBody:
      type: object
      properties:
        subjectType:
          type: string
          enum:
            - TENANT
            - TENANT_MEMBER
          description: TENANT = this organization; TENANT_MEMBER needs memberId.
        memberId:
          type: string
          description: TenantMember id — required for TENANT_MEMBER.
      required:
        - subjectType
    WalletBindingResponse:
      type: object
      properties:
        id:
          type: string
        identityId:
          type: string
        chainFamily:
          type: string
        chainId:
          type: object
        walletAddress:
          type: string
        walletType:
          type: string
          enum:
            - EOA
            - SMART_ACCOUNT
            - MULTISIG_SAFE
            - CUSTODIAL
        controllerType:
          type: string
          enum:
            - SELF
            - ORGANIZATION
            - SAFEBANK_CUSTODIAL
        verificationMethod:
          type: string
          enum:
            - SIGNATURE_CHALLENGE
            - SAFE_OWNERSHIP
            - PROVIDER_ATTESTED
            - MIGRATED
        status:
          type: string
          enum:
            - PENDING
            - ACTIVE
            - ROTATED
            - REVOKED
        validFrom:
          type: string
        validUntil:
          type: object
        verifiedAt:
          type: object
      required:
        - id
        - identityId
        - chainFamily
        - walletAddress
        - walletType
        - controllerType
        - verificationMethod
        - status
        - validFrom
    WalletBindingListResponse:
      type: object
      properties:
        wallets:
          type: array
          items:
            $ref: '#/components/schemas/WalletBindingResponse'
      required:
        - wallets
    CreateWalletBindingBody:
      type: object
      properties:
        walletAddress:
          type: string
          description: EVM address to bind (checksummed at rest).
        chainId:
          type: number
          description: EVM chain id the binding is scoped to.
        walletType:
          type: string
          enum:
            - EOA
            - SMART_ACCOUNT
            - MULTISIG_SAFE
      required:
        - walletAddress
    WalletBindingWithChallengeResponse:
      type: object
      properties:
        id:
          type: string
        identityId:
          type: string
        chainFamily:
          type: string
        chainId:
          type: object
        walletAddress:
          type: string
        walletType:
          type: string
          enum:
            - EOA
            - SMART_ACCOUNT
            - MULTISIG_SAFE
            - CUSTODIAL
        controllerType:
          type: string
          enum:
            - SELF
            - ORGANIZATION
            - SAFEBANK_CUSTODIAL
        verificationMethod:
          type: string
          enum:
            - SIGNATURE_CHALLENGE
            - SAFE_OWNERSHIP
            - PROVIDER_ATTESTED
            - MIGRATED
        status:
          type: string
          enum:
            - PENDING
            - ACTIVE
            - ROTATED
            - REVOKED
        validFrom:
          type: string
        validUntil:
          type: object
        verifiedAt:
          type: object
        challenge:
          type: string
          description: Sign this exact message (EIP-191 personal_sign) to verify.
      required:
        - id
        - identityId
        - chainFamily
        - walletAddress
        - walletType
        - controllerType
        - verificationMethod
        - status
        - validFrom
        - challenge
    VerifyWalletBindingBody:
      type: object
      properties:
        signature:
          type: string
          description: EIP-191 signature over the returned challenge.
      required:
        - signature
    RotateWalletBindingBody:
      type: object
      properties:
        newWalletAddress:
          type: string
          description: The replacement wallet address.
      required:
        - newWalletAddress
    CredentialResponse:
      type: object
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - PERSON_IDENTITY
            - BUSINESS_IDENTITY
            - DESTINATION_OWNERSHIP
            - ORGANIZATION_MEMBERSHIP
            - AUTHORITY_DELEGATION
            - PAYMENT_POLICY
        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
        subjectDid:
          type: object
          description: did:safebank subject, once identity-linked.
        credentialHash:
          type: object
          description: sha256 of the canonical signed document.
        issuerKind:
          type: string
          enum:
            - PLATFORM
            - ORGANIZATION
      required:
        - id
        - kind
        - subjectType
        - subjectId
        - status
        - legalName
        - issuedAt
    CredentialListResponse:
      type: object
      properties:
        credentials:
          type: array
          items:
            $ref: '#/components/schemas/CredentialResponse'
      required:
        - credentials
    CredentialStatusResponse:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
          enum:
            - ACTIVE
            - EXPIRED
            - REVOKED
        revokedReason:
          type: object
        statusRefreshedAt:
          type: object
          description: When the provider status was last observed.
        statusSource:
          type: object
          description: 'What refreshed it: issuance | webhook:* | poller | sync-refresh.'
      required:
        - id
        - status
    CredentialArtifactResponse:
      type: object
      properties:
        id:
          type: string
        document:
          type: object
          description: The canonical signed credential document.
        signature:
          type: string
          description: EIP-712 CredentialAttestation signature (hex).
        credentialHash:
          type: string
        nonce:
          type: string
          description: Replay nonce the attestation was signed with.
        issuerRef:
          type: string
          description: Issuer signer address (platform key or org Safe).
        schemaVersion:
          type: string
      required:
        - id
        - document
        - signature
        - credentialHash
        - nonce
        - issuerRef
        - schemaVersion
    HandoffResponse:
      type: object
      properties:
        url:
          type: string
          description: Permara-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).
    VerifyPayoutDestinationBody:
      type: object
      properties:
        signature:
          type: string
          description: >-
            EIP-712 DestinationOwnership signature by the key controlling the
            destination address (production proof; EVM only). Required when
            nonce or expiresAt is supplied.
        nonce:
          type: string
          description: >-
            Single-use nonce (uint256 decimal string). Required when signature
            is supplied.
        expiresAt:
          type: string
          description: >-
            Unix-seconds expiry of the proof. Required when signature is
            supplied.
    CreatePresentationChallengeBody:
      type: object
      properties:
        purpose:
          type: string
          example: payment.approve
          description: What the presentation authorizes.
        identityId:
          type: string
          description: Expected holder identity, when known.
        paymentId:
          type: string
          description: Payment the presentation is scoped to.
      required:
        - purpose
    PresentationChallengeResponse:
      type: object
      properties:
        nonce:
          type: string
          description: Single-use nonce; include verbatim in the signed message.
        audience:
          type: string
        domainTag:
          type: string
          description: EIP-712 domain verifyingContract to sign under.
        purpose:
          type: string
        expiresAt:
          type: string
      required:
        - nonce
        - audience
        - domainTag
        - purpose
        - expiresAt
    VerifyPresentationBody:
      type: object
      properties:
        nonce:
          type: string
        holderDid:
          type: string
          example: did:safebank:cabc…
        signature:
          type: string
          description: EIP-712 CredentialPresentation signature (hex).
        signerAddress:
          type: string
          description: The bound wallet that signed.
        credentialIds:
          description: Credential ids being presented.
          type: array
          items:
            type: string
        expiresAt:
          type: string
          description: ISO-8601 expiry the holder signed.
      required:
        - nonce
        - holderDid
        - signature
        - signerAddress
        - credentialIds
        - expiresAt
    PresentationCredentialResultResponse:
      type: object
      properties:
        credentialId:
          type: string
        credentialHash:
          type: string
        verdict:
          type: string
          enum:
            - VALID
            - EXPIRED
            - REVOKED
            - UNTRUSTED_ISSUER
            - SIGNATURE_INVALID
            - NOT_FOUND
      required:
        - credentialId
        - credentialHash
        - verdict
    PresentationVerdictResponse:
      type: object
      properties:
        verified:
          type: boolean
        holderDid:
          type: string
        credentialResults:
          type: array
          items:
            $ref: '#/components/schemas/PresentationCredentialResultResponse'
        reasonCodes:
          type: array
          items:
            type: string
        presentationId:
          type: string
      required:
        - verified
        - holderDid
        - credentialResults
        - reasonCodes
    AuthorityResponse:
      type: object
      properties:
        id:
          type: string
        memberId:
          type: string
        credentialId:
          type: object
          description: Backing AUTHORITY_DELEGATION credential once attested.
        allowedActions:
          type: array
          items:
            type: string
        perTxLimitUsd:
          type: object
        dailyLimitUsd:
          type: object
        monthlyLimitUsd:
          type: object
        allowedCurrencies:
          type: array
          items:
            type: string
        allowedAssets:
          type: array
          items:
            type: string
        allowedChains:
          type: array
          items:
            type: string
        allowedDestinationIds:
          type: array
          items:
            type: string
        approvalGroup:
          type: object
        approvalWeight:
          type: number
        requiresCountersignature:
          type: boolean
        countersignAboveUsd:
          type: object
        status:
          type: string
          enum:
            - ACTIVE
            - SUSPENDED
            - REVOKED
        validFrom:
          type: string
        validUntil:
          type: object
        version:
          type: number
      required:
        - id
        - memberId
        - allowedActions
        - allowedCurrencies
        - allowedAssets
        - allowedChains
        - allowedDestinationIds
        - approvalWeight
        - requiresCountersignature
        - status
        - validFrom
        - version
    AuthorityListResponse:
      type: object
      properties:
        authorities:
          type: array
          items:
            $ref: '#/components/schemas/AuthorityResponse'
      required:
        - authorities
    GrantAuthorityBody:
      type: object
      properties:
        allowedActions:
          example:
            - payment.create
            - payment.approve
          type: array
          items:
            type: string
        perTxLimitUsd:
          type: string
        dailyLimitUsd:
          type: string
        monthlyLimitUsd:
          type: string
        allowedCurrencies:
          type: array
          items:
            type: string
        allowedAssets:
          type: array
          items:
            type: string
        allowedChains:
          type: array
          items:
            type: string
        allowedDestinationIds:
          type: array
          items:
            type: string
        approvalGroup:
          type: string
        approvalWeight:
          type: number
          minimum: 1
        requiresCountersignature:
          type: boolean
        countersignAboveUsd:
          type: string
          description: Countersignature threshold within the per-tx limit.
        validUntil:
          type: string
          description: ISO-8601 expiry.
      required:
        - allowedActions
    AttestationInstructionsResponse:
      type: object
      properties:
        message:
          type: object
          description: The EIP-712 CredentialAttestation message to Safe-sign.
        primaryType:
          type: string
        note:
          type: string
      required:
        - message
        - primaryType
        - note
    AuthorityWithAttestationResponse:
      type: object
      properties:
        id:
          type: string
        memberId:
          type: string
        credentialId:
          type: object
          description: Backing AUTHORITY_DELEGATION credential once attested.
        allowedActions:
          type: array
          items:
            type: string
        perTxLimitUsd:
          type: object
        dailyLimitUsd:
          type: object
        monthlyLimitUsd:
          type: object
        allowedCurrencies:
          type: array
          items:
            type: string
        allowedAssets:
          type: array
          items:
            type: string
        allowedChains:
          type: array
          items:
            type: string
        allowedDestinationIds:
          type: array
          items:
            type: string
        approvalGroup:
          type: object
        approvalWeight:
          type: number
        requiresCountersignature:
          type: boolean
        countersignAboveUsd:
          type: object
        status:
          type: string
          enum:
            - ACTIVE
            - SUSPENDED
            - REVOKED
        validFrom:
          type: string
        validUntil:
          type: object
        version:
          type: number
        attestation:
          $ref: '#/components/schemas/AttestationInstructionsResponse'
      required:
        - id
        - memberId
        - allowedActions
        - allowedCurrencies
        - allowedAssets
        - allowedChains
        - allowedDestinationIds
        - approvalWeight
        - requiresCountersignature
        - status
        - validFrom
        - version
        - attestation
    UpdateAuthorityBody:
      type: object
      properties:
        status:
          type: string
          enum:
            - ACTIVE
            - SUSPENDED
        validUntil:
          type: object
          description: ISO-8601 expiry; null clears it.
    AttestBody:
      type: object
      properties:
        signature:
          type: string
          description: Safe signature blob over the attestation hash (EIP-1271).
      required:
        - signature
    PaymentPolicyResponse:
      type: object
      properties:
        policyId:
          type: object
        version:
          type: number
        rules:
          type: object
        status:
          type: string
          enum:
            - ACTIVE
            - SUPERSEDED
            - REVOKED
            - NONE
        credentialId:
          type: object
        activatedAt:
          type: object
      required:
        - version
        - rules
        - status
    PutPaymentPolicyBody:
      type: object
      properties:
        rules:
          type: object
          description: >-
            Structured rules: maximumFeeMinor, maximumAmountUsd, allowedRails,
            allowedAssets, allowedChains, approvals[{aboveUsd,
            minimumApprovals}]. Unknown keys are refused.
      required:
        - rules
    PaymentPolicyWithAttestationResponse:
      type: object
      properties:
        policyId:
          type: object
        version:
          type: number
        rules:
          type: object
        status:
          type: string
          enum:
            - ACTIVE
            - SUPERSEDED
            - REVOKED
            - NONE
        credentialId:
          type: object
        activatedAt:
          type: object
        attestation:
          $ref: '#/components/schemas/AttestationInstructionsResponse'
      required:
        - version
        - rules
        - status
        - attestation
    PaymentPolicyHistoryResponse:
      type: object
      properties:
        policies:
          type: array
          items:
            $ref: '#/components/schemas/PaymentPolicyResponse'
      required:
        - policies
    EvaluateEligibilityBody:
      type: object
      properties:
        action:
          type: string
          enum:
            - payments.create
            - invoices.pay
            - ramps.offramp
            - claims.claim
          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:
        id:
          type: string
        subjectAddress:
          type: string
        schema:
          type: string
        mode:
          type: string
          enum:
            - synthetic
            - plaid_sandbox
        maxPrincipal:
          type: string
        riskTier:
          type: number
        profile:
          type: object
          description: Synthetic financial evidence snapshot (demo data).
        permitDigest:
          type: string
        issuerAddress:
          type: string
        expiresAt:
          type: string
        status:
          type: string
      required:
        - id
        - subjectAddress
        - schema
        - mode
        - maxPrincipal
        - riskTier
        - profile
        - permitDigest
        - issuerAddress
        - expiresAt
        - status
    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
    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 Permara @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:
        account:
          nullable: true
          description: >-
            Where the money is (smart account + org account); null when
            unreadable.
          type: object
          allOf:
            - $ref: '#/components/schemas/BalanceResponse'
        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:
        - account
        - 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
    TemplateDescriptorResponse:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        inputs:
          type: array
          items:
            type: string
      required:
        - id
        - name
        - description
        - inputs
    TemplateListResponse:
      type: object
      properties:
        templates:
          type: array
          items:
            $ref: '#/components/schemas/TemplateDescriptorResponse'
      required:
        - templates
    AgreementPartyInput:
      type: object
      properties:
        partyKey:
          type: string
          description: Stable local key the policy references.
          example: payer
        role:
          type: string
          enum:
            - PAYER
            - BENEFICIARY
            - APPROVER
            - ATTESTOR
        displayName:
          type: string
          example: Mike Reynolds
        did:
          type: string
          description: Known Permara DID for this party.
        invitedEmail:
          type: string
          description: Email to invite an unbound party with.
      required:
        - partyKey
        - role
        - displayName
    CreateAgreementDraftBody:
      type: object
      properties:
        kind:
          type: string
          enum:
            - INVOICE
            - PAYMENT_REQUEST
            - PURCHASE
            - SERVICE
            - MILESTONE
            - TRANSFER
            - OTHER
          default: OTHER
        sourceType:
          type: string
          enum:
            - CHAT
            - TEMPLATE
            - EMAIL
            - UPLOAD
            - API
            - OTHER
          default: API
        title:
          type: string
          example: Boat sale to Mike
        description:
          type: string
        currency:
          type: string
          description: ISO currency or supported asset code.
          example: USD
        authorizedTotalMinor:
          type: string
          description: Authorized total in integer minor units.
          example: '8000000'
        parties:
          type: array
          items:
            $ref: '#/components/schemas/AgreementPartyInput'
      required:
        - title
        - currency
        - authorizedTotalMinor
        - parties
    AgreementPartyResponse:
      type: object
      properties:
        id:
          type: string
        partyKey:
          type: string
        role:
          type: string
        displayName:
          type: string
        verificationState:
          type: string
        did:
          type: object
        partyTenantId:
          type: string
          nullable: true
          description: Tenant bound to this party slot (after bind/claim).
        accepted:
          type: boolean
          description: >-
            True when this party holds an ACTIVE acceptance of the latest policy
            version.
      required:
        - id
        - partyKey
        - role
        - displayName
        - verificationState
    AgreementPolicyVersionResponse:
      type: object
      properties:
        id:
          type: string
        version:
          type: number
        status:
          type: string
        canonicalHash:
          type: string
        definition:
          type: object
          description: Compiled policy IR (safebank.agreement-policy/1.0).
      required:
        - id
        - version
        - status
        - canonicalHash
        - definition
    AgreementResponse:
      type: object
      properties:
        id:
          type: string
        publicReference:
          type: string
        kind:
          type: string
        sourceType:
          type: string
        title:
          type: string
        description:
          type: object
        currency:
          type: string
        authorizedTotalMinor:
          type: string
        lifecycleState:
          type: string
        acceptanceState:
          type: string
        fundingState:
          type: string
        settlementState:
          type: string
        disputeState:
          type: string
        riskStatus:
          type: string
        aggregateVersion:
          type: number
        parties:
          type: array
          items:
            $ref: '#/components/schemas/AgreementPartyResponse'
        latestPolicyVersion:
          $ref: '#/components/schemas/AgreementPolicyVersionResponse'
        createdAt:
          type: string
        updatedAt:
          type: string
      required:
        - id
        - publicReference
        - kind
        - sourceType
        - title
        - currency
        - authorizedTotalMinor
        - lifecycleState
        - acceptanceState
        - fundingState
        - settlementState
        - disputeState
        - riskStatus
        - aggregateVersion
        - parties
        - createdAt
        - updatedAt
    AgreementListResponse:
      type: object
      properties:
        agreements:
          type: array
          items:
            $ref: '#/components/schemas/AgreementResponse'
      required:
        - agreements
    FinancingRequestInput:
      type: object
      properties:
        requestedMinor:
          type: string
          description: Requested financing in integer minor units.
          example: '50000'
        coverage:
          type: string
          enum:
            - PURCHASE_PRICE
            - PURCHASE_PRICE_PLUS_COSTS
      required:
        - requestedMinor
        - coverage
    UpdateAgreementDraftBody:
      type: object
      properties:
        expectedVersion:
          type: number
          description: Optimistic concurrency token (aggregateVersion).
        financingRequest:
          nullable: true
          description: >-
            Set a financing request (intent only — nothing is applied for or
            funded) or pass null to withdraw it and pay from a funded source.
          type: object
          allOf:
            - $ref: '#/components/schemas/FinancingRequestInput'
        title:
          type: string
        description:
          type: string
        authorizedTotalMinor:
          type: string
          example: '8000000'
      required:
        - expectedVersion
    TemplateInvocation:
      type: object
      properties:
        templateId:
          type: string
          example: deposit-balance
        deadline:
          type: string
          description: Outer deadline (ISO-8601 UTC).
        inputs:
          type: object
          description: Template-specific inputs (validated by the compiler).
      required:
        - templateId
        - deadline
    CompilePolicyBody:
      type: object
      properties:
        authoredPolicy:
          type: object
          description: >-
            Authored policy document (safebank.agreement-policy/1.0 authored
            form). Deep validation happens in the deterministic compiler;
            failures return structured issues.
        template:
          $ref: '#/components/schemas/TemplateInvocation'
    PolicyIssueResponse:
      type: object
      properties:
        code:
          type: string
        path:
          type: string
        message:
          type: string
      required:
        - code
        - path
        - message
    CompilePolicyResponse:
      type: object
      properties:
        ok:
          type: boolean
        issues:
          type: array
          items:
            $ref: '#/components/schemas/PolicyIssueResponse'
        policyVersion:
          $ref: '#/components/schemas/AgreementPolicyVersionResponse'
      required:
        - ok
        - issues
    ValidatePolicyResponse:
      type: object
      properties:
        ok:
          type: boolean
        issues:
          type: array
          items:
            $ref: '#/components/schemas/PolicyIssueResponse'
        canonicalHash:
          type: string
      required:
        - ok
        - issues
    SimulateFactInput:
      type: object
      properties:
        requirementId:
          type: string
          example: balance-confirmed
        factType:
          type: string
          example: safebank.stage.confirmed.v1
      required:
        - requirementId
        - factType
    SimulateDisputeInput:
      type: object
      properties:
        affectedStageIds:
          description: Stage ids to dispute; omit for agreement-wide.
          type: array
          items:
            type: string
    SimulatePolicyBody:
      type: object
      properties:
        injectFacts:
          type: array
          items:
            $ref: '#/components/schemas/SimulateFactInput'
        at:
          type: string
          description: Evaluate as of this instant (ISO-8601 UTC).
        openDispute:
          $ref: '#/components/schemas/SimulateDisputeInput'
    SimulatePolicyResponse:
      type: object
      properties:
        before:
          type: object
          description: Decisions before the overlay.
        after:
          type: object
          description: Decisions after the overlay.
      required:
        - before
        - after
    FundingStepResponse:
      type: object
      properties:
        kind:
          type: string
          enum:
            - approve
            - fund
        to:
          type: string
        data:
          type: string
      required:
        - kind
        - to
        - data
    AgreementVersionResponse:
      type: object
      properties:
        id:
          type: string
        version:
          type: number
        docHash:
          type: string
        doc:
          type: object
          description: The canonical agreement terms document.
      required:
        - id
        - version
        - docHash
        - doc
    BindPartyBody:
      type: object
      properties:
        did:
          type: string
          description: Permara DID to bind this party to.
        reference:
          type: string
          description: >-
            The agreement publicReference — the shared-link capability a joining
            counterparty presents. Not needed for existing participants.
    AcceptAgreementBody:
      type: object
      properties:
        partyKey:
          type: string
          example: payee
        agreementHash:
          type: string
          description: Echo of the reviewed agreement version docHash.
        policyHash:
          type: string
          description: Echo of the reviewed policy canonicalHash.
      required:
        - partyKey
        - agreementHash
        - policyHash
    PreviewAuthorizationBody:
      type: object
      properties:
        scope:
          type: string
          enum:
            - ACTIVATION
            - RELEASE
        stageId:
          type: string
          description: Stage id (RELEASE scope only).
        payer:
          type: string
          description: The payer's registered member signer address.
        nonce:
          type: string
          description: Single-use uint256 nonce (decimal string).
        expiresAt:
          type: string
          description: Expiry (ISO-8601 UTC), max 90 days out.
      required:
        - scope
        - payer
        - nonce
        - expiresAt
    AuthorizationPreviewResponse:
      type: object
      properties:
        typedData:
          type: object
          description: EIP-712 typed-data the payer must sign.
      required:
        - typedData
    SubmitAuthorizationBody:
      type: object
      properties:
        scope:
          type: string
          enum:
            - ACTIVATION
            - RELEASE
        stageId:
          type: string
          description: Stage id (RELEASE scope only).
        payer:
          type: string
          description: The payer's registered member signer address.
        nonce:
          type: string
          description: Single-use uint256 nonce (decimal string).
        expiresAt:
          type: string
          description: Expiry (ISO-8601 UTC), max 90 days out.
        signature:
          type: string
          description: EIP-712 signature over the server-derived message.
      required:
        - scope
        - payer
        - nonce
        - expiresAt
        - signature
    AuthorizationResponse:
      type: object
      properties:
        id:
          type: string
        scope:
          type: string
        stageId:
          type: object
        status:
          type: string
        agreementHash:
          type: string
        policyHash:
          type: string
        releaseManifestHash:
          type: string
        canonicalPayloadHash:
          type: string
        expiresAt:
          type: string
      required:
        - id
        - scope
        - status
        - agreementHash
        - policyHash
        - releaseManifestHash
        - canonicalPayloadHash
        - expiresAt
    SubmitConfirmationBody:
      type: object
      properties:
        requirementId:
          type: string
          example: completion-confirmed
        confirmed:
          type: boolean
          description: true = the condition happened; false = it did not.
        note:
          type: string
      required:
        - requirementId
        - confirmed
    SubmitConfirmationResponse:
      type: object
      properties:
        observationId:
          type: string
        attestationId:
          type: string
      required:
        - observationId
        - attestationId
    RevokeAttestationBody:
      type: object
      properties:
        reason:
          type: string
          example: entered by mistake
      required:
        - reason
    PolicyConditionResponse:
      type: object
      properties:
        sentence:
          type: string
          example: Completion is confirmed by both sides
        detail:
          type: string
          example: You and Harbor Goods each confirm — evidence optional
        state:
          type: string
          enum:
            - 'true'
            - 'false'
            - pending
      required:
        - sentence
        - detail
        - state
    PolicyApprovalsResponse:
      type: object
      properties:
        required:
          type: number
        of:
          type: number
        names:
          type: array
          items:
            type: string
      required:
        - required
        - of
        - names
    PolicyRulesResponse:
      type: object
      properties:
        recipient:
          type: string
        amount:
          type: string
        paysWhen:
          type: string
        approvals:
          type: string
      required:
        - recipient
        - amount
        - paysWhen
        - approvals
    PolicyDraftResponse:
      type: object
      properties:
        agreementId:
          type: string
        templateId:
          type: string
          nullable: true
        documentRef:
          type: string
          nullable: true
        conditions:
          type: array
          items:
            $ref: '#/components/schemas/PolicyConditionResponse'
        approvals:
          $ref: '#/components/schemas/PolicyApprovalsResponse'
        rules:
          $ref: '#/components/schemas/PolicyRulesResponse'
      required:
        - agreementId
        - templateId
        - documentRef
        - conditions
        - approvals
        - rules
    PolicyStateApprovalsResponse:
      type: object
      properties:
        done:
          type: number
        of:
          type: number
        waitingOn:
          type: array
          items:
            type: string
        lastBy:
          type: string
          nullable: true
        at:
          type: string
          nullable: true
      required:
        - done
        - of
        - waitingOn
        - lastBy
        - at
    PolicyStateResponse:
      type: object
      properties:
        agreementId:
          type: string
        status:
          type: string
          enum:
            - DRAFT
            - PROPOSED
            - ACCEPTED
            - ACTIVE
            - PAUSED
            - RELEASED
        approvals:
          $ref: '#/components/schemas/PolicyStateApprovalsResponse'
      required:
        - agreementId
        - status
        - approvals
    AgreementPolicyDraftResponse:
      type: object
      properties:
        draft:
          $ref: '#/components/schemas/PolicyDraftResponse'
        state:
          $ref: '#/components/schemas/PolicyStateResponse'
      required:
        - draft
        - state
    AgreementDecisionResponse:
      type: object
      properties:
        id:
          type: string
        decisionHash:
          type: string
        inputSnapshotHash:
          type: string
        evaluatedAt:
          type: string
        result:
          type: object
          description: Full PolicyDecisionResult (stages, intents, reason codes).
      required:
        - id
        - decisionHash
        - inputSnapshotHash
        - evaluatedAt
        - result
    OpenDisputeBody:
      type: object
      properties:
        reasonCode:
          type: string
          example: ITEM_NOT_AS_DESCRIBED
        narrative:
          type: string
        affectedStageIds:
          description: Stage ids to freeze; omit for all unreleased stages.
          type: array
          items:
            type: string
      required:
        - reasonCode
    TimelineEntryResponse:
      type: object
      properties:
        at:
          type: string
        kind:
          type: string
          description: Internal event type this entry projects.
        label:
          type: string
          description: Plain-language line derived from machine state.
      required:
        - at
        - kind
        - label
    WhyWaitingResponse:
      type: object
      properties:
        stageId:
          type: string
        label:
          type: string
        state:
          type: string
        reasons:
          description: Exact unsatisfied conditions, in plain language.
          type: array
          items:
            type: string
      required:
        - stageId
        - label
        - state
        - reasons
    AgreementTimelineResponse:
      type: object
      properties:
        entries:
          type: array
          items:
            $ref: '#/components/schemas/TimelineEntryResponse'
        whyWaiting:
          type: array
          items:
            $ref: '#/components/schemas/WhyWaitingResponse'
      required:
        - entries
        - whyWaiting
    ShareLinkResponse:
      type: object
      properties:
        token:
          type: string
          description: Single-use claim token — shown once, sha256 stored.
        partyKey:
          type: string
        path:
          type: string
          description: Recipient path on app.permara.com (append to that origin).
        expiresAt:
          type: string
      required:
        - token
        - partyKey
        - path
        - expiresAt
    AgreementClaimPreviewResponse:
      type: object
      properties:
        partyKey:
          type: string
        partyDisplayName:
          type: string
        partyRole:
          type: string
        agreementId:
          type: string
        lifecycleState:
          type: string
        alreadyBound:
          type: boolean
          description: True when this slot has already been claimed.
        card:
          type: object
          description: The exact deterministic Agreement Card the parties accept.
      required:
        - partyKey
        - partyDisplayName
        - partyRole
        - agreementId
        - lifecycleState
        - alreadyBound
        - card
    AgreementJoinResponse:
      type: object
      properties:
        agreementId:
          type: string
        partyKey:
          type: string
      required:
        - agreementId
        - partyKey
    CreateEventSourceBody:
      type: object
      properties:
        name:
          type: string
          example: Title registry webhook
        requirementId:
          type: string
          description: The proof requirement this source attests (from the policy).
          example: title-transferred
      required:
        - name
        - requirementId
    CreateEventSourceResponse:
      type: object
      properties:
        id:
          type: string
        endpointId:
          type: string
          description: Reference this in WEBHOOK_ENDPOINT attestor selectors.
        name:
          type: string
        requirementId:
          type: string
        status:
          type: string
        secret:
          type: string
          description: HMAC secret — shown once, sealed at rest.
        ingestPath:
          type: string
      required:
        - id
        - endpointId
        - name
        - requirementId
        - status
        - secret
        - ingestPath
    EventSourceItemResponse:
      type: object
      properties:
        id:
          type: string
        endpointId:
          type: string
        name:
          type: string
        requirementId:
          type: string
        status:
          type: string
        createdAt:
          type: string
      required:
        - id
        - endpointId
        - name
        - requirementId
        - status
        - createdAt
    EventSourceListResponse:
      type: object
      properties:
        sources:
          type: array
          items:
            $ref: '#/components/schemas/EventSourceItemResponse'
      required:
        - sources
    RotateSecretResponse:
      type: object
      properties:
        secret:
          type: string
          description: The new HMAC secret — shown once.
      required:
        - secret
    EventSourceDeliveriesResponse:
      type: object
      properties:
        deliveries:
          type: array
          items:
            type: object
      required:
        - deliveries
    EventSourceTestSampleResponse:
      type: object
      properties:
        method:
          type: string
        path:
          type: string
        headers:
          type: object
        body:
          type: string
      required:
        - method
        - path
        - headers
        - body
    IngestEventResponse:
      type: object
      properties:
        observationId:
          type: string
        attestationId:
          type: string
      required:
        - observationId
        - attestationId
    ChatToolManifestResponse:
      type: object
      properties:
        schemaVersion:
          type: string
        generatedFor:
          type: string
          enum:
            - principal
            - artifact
        writesEnabled:
          type: boolean
        tools:
          description: Tools this principal may invoke on this surface.
          type: array
          items:
            type: object
      required:
        - schemaVersion
        - generatedFor
        - writesEnabled
        - tools
    ChatSessionSummaryResponse:
      type: object
      properties:
        sessionId:
          type: string
        title:
          type: object
          nullable: true
          description: Derived from the first words; editable.
        kind:
          type: string
          enum:
            - pay
            - agreement
            - invoice
            - financing
            - general
        agreementId:
          type: object
          nullable: true
        paymentId:
          type: object
          nullable: true
        extractionId:
          type: object
          nullable: true
        pinnedAt:
          type: object
          nullable: true
        lastActivityAt:
          type: string
          description: ISO timestamp of the last turn or link; the list orders by it.
        lastReply:
          type: object
          nullable: true
          description: The assistant's last reply, trimmed.
        attention:
          type: string
          nullable: true
          enum:
            - SIGNATURE
            - CLARIFICATION
            - REVIEW
            - OFFER
          description: >-
            Why the conversation needs the person; null when nothing waits on
            them.
        createdAt:
          type: string
      required:
        - sessionId
        - kind
        - lastActivityAt
        - createdAt
    ChatSessionListResponse:
      type: object
      properties:
        sessions:
          type: array
          items:
            $ref: '#/components/schemas/ChatSessionSummaryResponse'
        nextBefore:
          type: object
          nullable: true
          description: >-
            Pass as ?before= to fetch the next (older) page; null on the last
            page.
      required:
        - sessions
    CreateChatSessionBody:
      type: object
      properties:
        text:
          type: string
          description: >-
            The person's first words. When present the first turn runs at once
            and the response returns before it finishes — open the session and
            stream events from cursor 0.
          example: pay @harborgoods 250 for the rope order
        attachments:
          description: >-
            Upload ids attached to the first message (see POST
            /v1/payables/uploads).
          type: array
          items:
            type: string
        clientTurnId:
          type: string
          description: Idempotency id for the first turn (default "first").
    ChatSessionResponse:
      type: object
      properties:
        sessionId:
          type: string
        title:
          type: object
          nullable: true
          description: Derived from the first words; editable.
        kind:
          type: string
          enum:
            - pay
            - agreement
            - invoice
            - financing
            - general
        agreementId:
          type: object
          nullable: true
        paymentId:
          type: object
          nullable: true
        extractionId:
          type: object
          nullable: true
        pinnedAt:
          type: object
          nullable: true
        lastActivityAt:
          type: string
          description: ISO timestamp of the last turn or link; the list orders by it.
        lastReply:
          type: object
          nullable: true
          description: The assistant's last reply, trimmed.
        attention:
          type: string
          nullable: true
          enum:
            - SIGNATURE
            - CLARIFICATION
            - REVIEW
            - OFFER
          description: >-
            Why the conversation needs the person; null when nothing waits on
            them.
        createdAt:
          type: string
        provider:
          type: string
          enum:
            - anthropic
            - openai
            - deepseek
            - mock
        model:
          type: string
        promptVersion:
          type: string
      required:
        - sessionId
        - kind
        - lastActivityAt
        - createdAt
        - provider
        - model
        - promptVersion
    PatchChatSessionBody:
      type: object
      properties:
        title:
          type: string
          description: A new title for the conversation (1..80 characters).
        pinned:
          type: boolean
          description: Pin (true) or unpin (false) on the Home list.
    LinkChatPaymentBody:
      type: object
      properties:
        paymentId:
          type: string
          description: >-
            The corridor payment the person signed from this conversation (POST
            /v1/offramp/payments). Must belong to the caller.
      required:
        - paymentId
    ChatTranscriptMessageResponse:
      type: object
      properties:
        seq:
          type: number
        role:
          type: string
          enum:
            - user
            - assistant
        text:
          type: string
        toolCalls:
          description: Tools invoked in this turn.
          type: array
          items:
            type: string
      required:
        - seq
        - role
        - text
        - toolCalls
    ChatTranscriptResponse:
      type: object
      properties:
        sessionId:
          type: string
        title:
          type: object
          nullable: true
          description: Derived from the first words; editable.
        kind:
          type: string
          enum:
            - pay
            - agreement
            - invoice
            - financing
            - general
        agreementId:
          type: object
          nullable: true
        paymentId:
          type: object
          nullable: true
        extractionId:
          type: object
          nullable: true
        pinnedAt:
          type: object
          nullable: true
        lastActivityAt:
          type: string
          description: ISO timestamp of the last turn or link; the list orders by it.
        lastReply:
          type: object
          nullable: true
          description: The assistant's last reply, trimmed.
        attention:
          type: string
          nullable: true
          enum:
            - SIGNATURE
            - CLARIFICATION
            - REVIEW
            - OFFER
          description: >-
            Why the conversation needs the person; null when nothing waits on
            them.
        createdAt:
          type: string
        provider:
          type: string
          enum:
            - anthropic
            - openai
            - deepseek
            - mock
        model:
          type: string
        promptVersion:
          type: string
        messages:
          type: array
          items:
            $ref: '#/components/schemas/ChatTranscriptMessageResponse'
      required:
        - sessionId
        - kind
        - lastActivityAt
        - createdAt
        - provider
        - model
        - promptVersion
        - messages
    SendChatMessageBody:
      type: object
      properties:
        text:
          type: string
          description: >-
            The user message. Quoted documents are treated as data, never
            instructions.
          example: >-
            I'm selling my boat to Mike Reynolds for $80,000 — $10,000 deposit
            when we sign.
        mode:
          type: string
          enum:
            - execute
            - plan
          description: >-
            plan = read tools run, write tools are returned as proposals with
            zero side effects.
        clientTurnId:
          type: string
          description: >-
            Client-chosen idempotency id (unique per session). A replay returns
            the stored result; the same id with different content is a 409.
        hostConfirmations:
          description: >-
            Tool names the HOST confirmed with the user for this turn (e.g.
            send_for_review). Host-required tools refuse without it; model
            output can never supply it.
          type: array
          items:
            type: string
        attachments:
          description: >-
            Upload ids (POST /v1/payables/uploads) attached to this message.
            Verified to belong to the caller before the assistant sees them; the
            assistant reads them with prepare_document — bytes never enter the
            model context.
          type: array
          items:
            type: string
      required:
        - text
    ChatActionResponse:
      type: object
      properties:
        tool:
          type: string
          description: Allowlisted tool the assistant invoked.
        ok:
          type: boolean
        summary:
          type: string
          description: Human-readable outcome of the server-validated call.
        resultId:
          type: string
          description: Tool result id (envelope agreements-ai.tool-result/1).
        proposed:
          type: boolean
          description: True when a write was proposed in plan mode, not executed.
        reasonCodes:
          description: >-
            Stable reason codes from the tool result envelope (e.g.
            HOST_CONFIRMATION_REQUIRED, PLAN_ONLY). Match on these, never on
            summary wording.
          type: array
          items:
            type: string
      required:
        - tool
        - ok
        - summary
        - reasonCodes
    ChatProviderMetaResponse:
      type: object
      properties:
        provider:
          type: string
        model:
          type: string
        responseId:
          type: object
          nullable: true
        reportedRevision:
          type: object
          nullable: true
      required:
        - provider
        - model
    ChatTurnLimitsResponse:
      type: object
      properties:
        toolCalls:
          type: number
        elapsedMs:
          type: number
        exceeded:
          type: object
          nullable: true
          description: Budget that ended the turn early, if any.
      required:
        - toolCalls
        - elapsedMs
    SendChatMessageResponse:
      type: object
      properties:
        sessionId:
          type: string
        resultId:
          type: string
          description: Stable id of this turn result (replays return the same id).
        correlationId:
          type: string
          description: Request id correlating logs, tool results and this turn.
        mode:
          type: string
          enum:
            - execute
            - plan
        replayed:
          type: boolean
          description: >-
            True when this response is the stored result of an earlier identical
            turn.
        reply:
          type: string
          description: The assistant's reply text.
        actions:
          type: array
          items:
            $ref: '#/components/schemas/ChatActionResponse'
        agreementId:
          type: object
          description: Draft created/edited in this conversation.
        card:
          type: object
          description: >-
            Deterministic Agreement Card for the draft (server-computed, never
            AI prose).
        clarifications:
          description: >-
            Open questions ordered by execution risk (deterministic; never model
            confidence).
          type: array
          items:
            type: object
        reviewUrl:
          type: object
          nullable: true
          description: Trusted review surface. Opening it never accepts or executes.
        providerMeta:
          nullable: true
          type: object
          allOf:
            - $ref: '#/components/schemas/ChatProviderMetaResponse'
        limits:
          $ref: '#/components/schemas/ChatTurnLimitsResponse'
      required:
        - sessionId
        - resultId
        - correlationId
        - mode
        - replayed
        - reply
        - actions
        - clarifications
        - limits
    ChatEventResponse:
      type: object
      properties:
        seq:
          type: number
          description: Monotonic per-session sequence; resume with ?cursor=<seq>.
        type:
          type: string
          enum:
            - turn_started
            - message_delta
            - tool_result
            - agreement_updated
            - clarification_required
            - document_ready
            - financing_offer
            - financing_funded
            - verification_updated
            - review_required
            - approval_updated
            - execution_updated
            - recoverable_error
            - turn_completed
            - pay_draft
            - payment_linked
        sessionId:
          type: string
        correlationId:
          type: string
        clientTurnId:
          type: string
          nullable: true
        at:
          type: string
          description: ISO timestamp.
        objectRefs:
          description: Canonical object ids/versions this event refers to.
          type: array
          items:
            type: object
        payload:
          type: object
          description: Safe structured payload (see docs/agent-platform/event-schemas.md).
      required:
        - seq
        - type
        - sessionId
        - correlationId
        - at
        - objectRefs
        - payload
    CancelChatTurnResponse:
      type: object
      properties:
        status:
          type: string
          enum:
            - cancel_requested
            - already_completed
            - already_requested
        note:
          type: string
          description: >-
            Cancelling generation never cancels a submitted operation; the draft
            written so far stands.
      required:
        - status
        - note
    PayablesAddressResponse:
      type: object
      properties:
        address:
          type: string
          example: ap-x8k2m4p9q1z7@payables.permara.com
        localPart:
          type: string
      required:
        - address
        - localPart
    SimulatedAttachmentInput:
      type: object
      properties:
        filename:
          type: string
          example: invoice.txt
        contentType:
          type: string
          example: text/plain
        base64:
          type: string
          description: Base64 attachment bytes (max ~1MB).
      required:
        - filename
        - contentType
        - base64
    SimulateInboundBody:
      type: object
      properties:
        from:
          type: string
          example: Acme Marine <billing@acmemarine.example>
        subject:
          type: string
          example: Invoice 2041 — slip rental
        text:
          type: string
          description: Plain-text body.
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/SimulatedAttachmentInput'
      required:
        - from
    SimulateInboundResponse:
      type: object
      properties:
        messageId:
          type: string
        status:
          type: string
        extractionId:
          type: object
      required:
        - messageId
        - status
    UploadDocumentBody:
      type: object
      properties:
        filename:
          type: string
          example: invoice-2041.pdf
        contentType:
          type: string
          enum:
            - application/pdf
            - image/png
            - image/jpeg
            - text/plain
            - text/csv
        base64:
          type: string
          description: Base64 file bytes (max ~1MB). Bytes never transit a URL or a shell.
      required:
        - filename
        - contentType
        - base64
    UploadDocumentResponse:
      type: object
      properties:
        uploadId:
          type: string
          description: Reference for `prepare_document` / the chat.
        status:
          type: string
          enum:
            - EXTRACTED
            - NEEDS_REVIEW
            - QUARANTINED
            - REJECTED
        extractionId:
          type: string
          nullable: true
        sha256:
          type: string
          description: sha256 of the uploaded bytes (integrity hash).
      required:
        - uploadId
        - status
        - sha256
    InboundMessagesResponse:
      type: object
      properties:
        messages:
          type: array
          items:
            type: object
      required:
        - messages
    ExtractionFactDto:
      type: object
      properties:
        key:
          type: string
          description: >-
            vendorName | totalMinor | description | dueDate | invoiceNumber |
            poNumber | paymentTerms | issueDate | buyerName | subtotalMinor |
            taxMinor
        label:
          type: string
          description: >-
            From | Amount | For | Due | Invoice | PO number | Terms | Issued |
            Billed to | Subtotal | Tax
        value:
          type: string
          nullable: true
        display:
          type: string
          nullable: true
          example: $18,500 Demo USD
        confidence:
          type: number
          nullable: true
          description: 0..1
        note:
          type: string
          nullable: true
          description: The reader's one-line reason, e.g. 'smudged scan'.
        source:
          type: string
          enum:
            - read
            - corrected
      required:
        - key
        - label
        - value
        - display
        - confidence
        - note
        - source
    ExtractionCounterpartyDto:
      type: object
      properties:
        name:
          type: string
          nullable: true
        verified:
          type: boolean
          nullable: true
          description: >-
            Joined from the identity side, never asserted by the document. Null
            until that join ships.
      required:
        - name
        - verified
    ExtractionMismatchDto:
      type: object
      properties:
        field:
          type: string
        document:
          type: string
        draft:
          type: string
      required:
        - field
        - document
        - draft
    ExtractionFactsDto:
      type: object
      properties:
        id:
          type: string
        extractionId:
          type: string
          description: Same as id — the name the conversation events use.
        uploadId:
          type: string
          description: The upload / inbound message the document came from.
        status:
          type: string
          enum:
            - EXTRACTED
            - NEEDS_REVIEW
            - APPROVED
            - REJECTED
        provider:
          type: string
          enum:
            - mock
            - textract
            - llm
        kind:
          type: string
          enum:
            - INVOICE
            - PURCHASE_ORDER
          nullable: true
        title:
          type: string
          nullable: true
          example: INV-2291 · ABC Construction · $18,500
        facts:
          type: array
          items:
            $ref: '#/components/schemas/ExtractionFactDto'
        counterparty:
          $ref: '#/components/schemas/ExtractionCounterpartyDto'
        mismatches:
          type: array
          items:
            $ref: '#/components/schemas/ExtractionMismatchDto'
        fieldsJson:
          type: object
          description: >-
            The raw per-field extraction (value + confidence per key) — the
            source the facts are built from.
        agreementId:
          type: string
          nullable: true
        duplicateOfId:
          type: string
          nullable: true
        createdAt:
          type: string
      required:
        - id
        - extractionId
        - uploadId
        - status
        - provider
        - kind
        - title
        - facts
        - counterparty
        - mismatches
        - fieldsJson
        - agreementId
        - duplicateOfId
        - createdAt
    ExtractionFlagDto:
      type: object
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - LOW_CONFIDENCE
            - DUPLICATE_INVOICE
            - CHANGED_BANK_DETAILS
        status:
          type: string
          enum:
            - OPEN
            - RESOLVED
            - DISMISSED
        field:
          type: string
          description: The field the flag is about, when it is about one.
        detail:
          type: string
          description: One sentence the face can show as-is.
        detailsJson:
          type: object
          nullable: true
      required:
        - id
        - kind
        - status
    ExtractionListItemResponse:
      type: object
      properties:
        extraction:
          $ref: '#/components/schemas/ExtractionFactsDto'
        flags:
          type: array
          items:
            $ref: '#/components/schemas/ExtractionFlagDto'
      required:
        - extraction
        - flags
    ExtractionListResponse:
      type: object
      properties:
        extractions:
          type: array
          items:
            $ref: '#/components/schemas/ExtractionListItemResponse'
      required:
        - extractions
    ExtractionDetailResponse:
      type: object
      properties:
        extraction:
          $ref: '#/components/schemas/ExtractionFactsDto'
        flags:
          type: array
          items:
            $ref: '#/components/schemas/ExtractionFlagDto'
      required:
        - extraction
        - flags
    CorrectExtractionBody:
      type: object
      properties:
        field:
          type: string
          enum:
            - vendorName
            - vendorEmail
            - invoiceNumber
            - currency
            - totalMinor
            - dueDate
            - bankDetails
            - poNumber
            - paymentTerms
            - issueDate
            - subtotalMinor
            - taxMinor
            - buyerName
        value:
          type: string
      required:
        - field
        - value
    ApproveExtractionResponse:
      type: object
      properties:
        agreementId:
          type: string
      required:
        - agreementId
    ResolveFlagBody:
      type: object
      properties:
        outcome:
          type: string
          enum:
            - RESOLVED
            - DISMISSED
      required:
        - outcome
    CreatePoolBody:
      type: object
      properties:
        name:
          type: string
          example: Demo PO Pool A
        subscriptionSeconds:
          type: number
          description: Subscription window length in seconds (default 7 days).
          example: 604800
        originationSeconds:
          type: number
          description: >-
            Origination window length in seconds after subscription close
            (default 30 days).
          example: 2592000
      required:
        - name
    PoolStateResponse:
      type: object
      properties:
        totalUnits:
          type: string
        frozenTotalUnits:
          type: string
        openingCapital:
          type: string
        accountedCash:
          type: string
        principalCash:
          type: string
        realizedInterestCash:
          type: string
        performingPrincipal:
          type: string
        defaultedPrincipal:
          type: string
        cumulativePrincipalOriginated:
          type: string
        cumulativePrincipalReceived:
          type: string
        cumulativePrincipalWrittenDown:
          type: string
        cumulativePrincipalRecovered:
          type: string
        cumulativeDistributable:
          type: string
        availableForNewLoans:
          type: string
      required:
        - totalUnits
        - frozenTotalUnits
        - openingCapital
        - accountedCash
        - principalCash
        - realizedInterestCash
        - performingPrincipal
        - defaultedPrincipal
        - cumulativePrincipalOriginated
        - cumulativePrincipalReceived
        - cumulativePrincipalWrittenDown
        - cumulativePrincipalRecovered
        - cumulativeDistributable
        - availableForNewLoans
    PoolResponse:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        chainId:
          type: number
        provider:
          type: string
        poolAddress:
          type: string
        tokenAddress:
          type: string
        registryAddress:
          type: string
        phase:
          type: string
        outcome:
          type: string
        subscriptionOpenAt:
          type: string
        subscriptionCloseAt:
          type: string
        originationEndAt:
          type: string
        minimumRaise:
          type: string
        subscriptionCap:
          type: string
        perLoanCap:
          type: string
        absoluteExposureCap:
          type: string
        exposureBps:
          type: number
        reserveBps:
          type: number
        annualRateBps:
          type: number
        tenorSeconds:
          type: number
        graceSeconds:
          type: number
        state:
          $ref: '#/components/schemas/PoolStateResponse'
        environmentNotice:
          type: string
          description: >-
            Test funds only. These tokens have no real monetary value;
            contributions lock at subscription close and repayment is not
            guaranteed.
      required:
        - id
        - name
        - chainId
        - provider
        - poolAddress
        - tokenAddress
        - registryAddress
        - phase
        - outcome
        - subscriptionOpenAt
        - subscriptionCloseAt
        - originationEndAt
        - minimumRaise
        - subscriptionCap
        - perLoanCap
        - absoluteExposureCap
        - exposureBps
        - reserveBps
        - annualRateBps
        - tenorSeconds
        - graceSeconds
        - state
        - environmentNotice
    PoolListResponse:
      type: object
      properties:
        pools:
          type: array
          items:
            $ref: '#/components/schemas/PoolResponse'
      required:
        - pools
    PositionResponse:
      type: object
      properties:
        poolId:
          type: string
        ownerAddress:
          type: string
        units:
          type: string
        deposited:
          type: string
        withdrawn:
          type: string
        claimed:
          type: string
        claimable:
          type: string
        phase:
          type: string
      required:
        - poolId
        - ownerAddress
        - units
        - deposited
        - withdrawn
        - claimed
        - claimable
        - phase
    AmountBody:
      type: object
      properties:
        amount:
          type: string
          description: Six-decimal integer atoms.
          example: '60000000000'
      required:
        - amount
    TxResponse:
      type: object
      properties:
        txRef:
          type: string
      required:
        - txRef
    ClaimResponse:
      type: object
      properties:
        txRef:
          type: string
        amount:
          type: string
          description: Amount paid to the recorded owner wallet (atoms).
      required:
        - txRef
        - amount
    RegisterPoBody:
      type: object
      properties:
        poolId:
          type: string
          description: Pool whose shared registry records the PO.
        tradeOrderId:
          type: string
          description: >-
            A mutually signed Verified Sales Order (ADR 0028) — the preferred
            intake. The financing lock keys to its salesOrderHash; seller
            wallet, borrower and face amount derive from the order, and the
            pool-domain supplier acceptance is signed automatically (the
            commercial consent already exists in the SO).
        extractionId:
          type: string
          description: 'Legacy intake: an APPROVED payables extraction id.'
        supplierTenantId:
          type: string
          description: >-
            Supplier tenant slug/id — the supplier's demo wallet is derived from
            it. Exactly one of supplierTenantId or supplierWallet is required.
        supplierWallet:
          type: string
          description: Explicit supplier payout wallet.
        faceAmount:
          type: string
          description: PO face amount in atoms; defaults to the extracted invoice amount.
          example: '20000000000'
      required:
        - poolId
    PoResponse:
      type: object
      properties:
        id:
          type: string
        poId:
          type: string
        extractionId:
          type: string
          nullable: true
        tradeOrderId:
          type: string
          nullable: true
          description: Verified Sales Order behind this PO.
        borrowerAddress:
          type: string
        supplierWallet:
          type: string
        faceAmount:
          type: string
        status:
          type: string
      required:
        - id
        - poId
        - extractionId
        - tradeOrderId
        - borrowerAddress
        - supplierWallet
        - faceAmount
        - status
    PoListResponse:
      type: object
      properties:
        purchaseOrders:
          type: array
          items:
            $ref: '#/components/schemas/PoResponse'
      required:
        - purchaseOrders
    AcceptPoBody:
      type: object
      properties:
        minPrincipal:
          type: string
          description: >-
            Supplier floor on financed principal (atoms); defaults to the PO
            face amount.
          example: '20000000000'
    IssueEligibilityBody:
      type: object
      properties:
        poolId:
          type: string
          description: Pool the permit is domain-bound to (EIP-712 anti-replay).
        mode:
          type: string
          enum:
            - synthetic
            - plaid_sandbox
          description: >-
            Evidence mode, named explicitly. synthetic = deterministic demo
            data; plaid_sandbox = a real token-exchange pipeline against the
            Plaid sandbox (503 when PLAID_CLIENT_ID/PLAID_SECRET are unset).
            Neither is ever a silent fallback for the other.
        riskTier:
          type: number
          description: Synthetic risk tier 1 (best) – 5.
          example: 2
      required:
        - poolId
        - mode
    CreateOfferBody:
      type: object
      properties:
        poolId:
          type: string
        poId:
          type: string
          description: Canonical registered PO id (0x…32 bytes).
        principal:
          type: string
          description: Requested principal in atoms.
          example: '20000000000'
      required:
        - poolId
        - poId
        - principal
    OfferResponse:
      type: object
      properties:
        id:
          type: string
        poolId:
          type: string
        poId:
          type: string
        borrowerAddress:
          type: string
        principal:
          type: string
        rateBps:
          type: number
        tenorSeconds:
          type: number
        graceSeconds:
          type: number
        deadline:
          type: string
        offerDigest:
          type: string
        status:
          type: string
      required:
        - id
        - poolId
        - poId
        - borrowerAddress
        - principal
        - rateBps
        - tenorSeconds
        - graceSeconds
        - deadline
        - offerDigest
        - status
    FinanceBody:
      type: object
      properties:
        offerId:
          type: string
          description: An ACCEPTED lending offer id.
      required:
        - offerId
    LoanResponse:
      type: object
      properties:
        id:
          type: string
        poolId:
          type: string
        poId:
          type: string
        borrowerAddress:
          type: string
        supplierWallet:
          type: string
        principal:
          type: string
        principalOutstanding:
          type: string
        interestDue:
          type: string
        status:
          type: string
        fundedAt:
          type: string
        maturityAt:
          type: string
        graceEndsAt:
          type: string
      required:
        - id
        - poolId
        - poId
        - borrowerAddress
        - supplierWallet
        - principal
        - principalOutstanding
        - interestDue
        - status
        - fundedAt
        - maturityAt
        - graceEndsAt
    LoanListResponse:
      type: object
      properties:
        loans:
          type: array
          items:
            $ref: '#/components/schemas/LoanResponse'
      required:
        - loans
    RepayBody:
      type: object
      properties:
        maxAmount:
          type: string
          description: Maximum the payer authorizes (atoms).
          example: '20197260273'
        deadlineSeconds:
          type: number
          description: Authorization validity in seconds (default 3600).
          example: 3600
      required:
        - maxAmount
    RepaymentResponse:
      type: object
      properties:
        txRef:
          type: string
        amount:
          type: string
        interestPortion:
          type: string
        principalPortion:
          type: string
        loanStatus:
          type: string
      required:
        - txRef
        - amount
        - interestPortion
        - principalPortion
        - loanStatus
    AdvanceClockBody:
      type: object
      properties:
        seconds:
          type: number
          description: Seconds to advance the mock chain clock.
          example: 604800
      required:
        - seconds
    ClockResponse:
      type: object
      properties:
        now:
          type: number
          description: Unix seconds now shown by the mock chain.
      required:
        - now
    IssuerStatusResponse:
      type: object
      properties:
        issuerEpoch:
          type: number
        statusCheckpointAt:
          type: number
        maxStatusAgeSeconds:
          type: number
        txRef:
          type: string
      required:
        - issuerEpoch
        - statusCheckpointAt
        - maxStatusAgeSeconds
        - txRef
    SetIssuerBudgetBody:
      type: object
      properties:
        maxOutstanding:
          type: string
          description: Hard cap on total outstanding financed principal (atoms).
        capacity:
          type: string
          description: Token-bucket burst capacity for gross draws (atoms).
        refillPerSecond:
          type: string
          description: Bucket refill per second (atoms; 0 = pure burst budget).
      required:
        - maxOutstanding
        - capacity
        - refillPerSecond
    IssuerBudgetResponse:
      type: object
      properties:
        maxOutstanding:
          type: string
        capacity:
          type: string
        refillPerSecond:
          type: string
        currentLevel:
          type: string
        outstanding:
          type: string
        configured:
          type: boolean
        txRef:
          type: string
      required:
        - maxOutstanding
        - capacity
        - refillPerSecond
        - currentLevel
        - outstanding
        - configured
        - txRef
    TradeLineItemInput:
      type: object
      properties:
        description:
          type: string
          example: Marine diesel engines
        amountAtoms:
          type: string
          description: Line amount in atoms.
          example: '20000000000'
      required:
        - description
        - amountAtoms
    TradeTermsInput:
      type: object
      properties:
        lineItems:
          type: array
          items:
            $ref: '#/components/schemas/TradeLineItemInput'
        dueDays:
          type: number
          description: Days until the invoice is due after acceptance.
          example: 14
        deliveryTerms:
          type: string
          example: FOB destination, 2 crates
      required:
        - lineItems
    CreateTradeOrderBody:
      type: object
      properties:
        sellerTenantId:
          type: string
          description: Seller tenant id — resolves the seller DID and demo wallet.
        extractionId:
          type: string
          description: >-
            Seed the draft terms from an APPROVED payables extraction (uploaded
            PO).
        agreementId:
          type: string
          description: >-
            Seed the draft from an agreement's financing request (ADR 0026 §5):
            total = requestedMinor, seller must match the beneficiary party's
            tenant. Financing the resulting sales order fulfills the agreement's
            request.
        terms:
          $ref: '#/components/schemas/TradeTermsInput'
      required:
        - sellerTenantId
    TradeOrderResponse:
      type: object
      properties:
        id:
          type: string
        number:
          type: string
        status:
          type: string
        version:
          type: number
        buyerTenantId:
          type: string
        sellerTenantId:
          type: string
        buyerDid:
          type: string
        sellerDid:
          type: string
        totalAtoms:
          type: string
        currency:
          type: string
        dueDays:
          type: number
        terms:
          type: object
        contentHash:
          type: string
        changeNote:
          type: string
          nullable: true
        buyerSigned:
          type: boolean
          description: Buyer has signed this exact version.
        sellerSigned:
          type: boolean
          description: Seller has countersigned the same version.
        salesOrderHash:
          type: string
          nullable: true
        agreementId:
          type: string
          nullable: true
          description: >-
            Agreement whose financing request this order fulfills (ADR 0026 §5
            bridge).
        financeable:
          type: boolean
          description: True once mutually signed — the financeable link document.
      required:
        - id
        - number
        - status
        - version
        - buyerTenantId
        - sellerTenantId
        - buyerDid
        - sellerDid
        - totalAtoms
        - currency
        - dueDays
        - terms
        - contentHash
        - changeNote
        - buyerSigned
        - sellerSigned
        - salesOrderHash
        - agreementId
        - financeable
    TradeOrderListResponse:
      type: object
      properties:
        orders:
          type: array
          items:
            $ref: '#/components/schemas/TradeOrderResponse'
      required:
        - orders
    UpdateTradeOrderBody:
      type: object
      properties:
        terms:
          $ref: '#/components/schemas/TradeTermsInput'
      required:
        - terms
    ReviewTradeOrderBody:
      type: object
      properties:
        action:
          type: string
          enum:
            - accept
            - request-changes
            - reject
        note:
          type: string
          description: Required note for request-changes.
      required:
        - action
    DeliverBody:
      type: object
      properties:
        note:
          type: string
          example: Delivered to pier 4, signed by receiving manager.
        evidenceUploadIds:
          description: Payables upload ids attached as delivery evidence.
          type: array
          items:
            type: string
    TradeDeliveryResponse:
      type: object
      properties:
        id:
          type: string
        orderId:
          type: string
        status:
          type: string
        note:
          type: string
          nullable: true
        evidenceHash:
          type: string
        deliveredAt:
          type: string
        acceptedAt:
          type: string
          nullable: true
        invoiceId:
          type: string
          nullable: true
          description: Invoice generated by the buyer acceptance, when accepted.
      required:
        - id
        - orderId
        - status
        - note
        - evidenceHash
        - deliveredAt
        - acceptedAt
        - invoiceId
    TradeInvoiceResponse:
      type: object
      properties:
        id:
          type: string
        orderId:
          type: string
        invoiceNumber:
          type: string
        amountAtoms:
          type: string
        dueAt:
          type: string
        status:
          type: string
        matchedChecks:
          type: object
          nullable: true
      required:
        - id
        - orderId
        - invoiceNumber
        - amountAtoms
        - dueAt
        - status
        - matchedChecks
    TradeInvoiceListResponse:
      type: object
      properties:
        invoices:
          type: array
          items:
            $ref: '#/components/schemas/TradeInvoiceResponse'
      required:
        - invoices
    TradePaymentResponse:
      type: object
      properties:
        id:
          type: string
        invoiceId:
          type: string
        route:
          type: string
          enum:
            - FINANCIER
            - SELLER
        amountAtoms:
          type: string
        interestPortion:
          type: string
        principalPortion:
          type: string
        sellerPortion:
          type: string
        remainingFinancingCost:
          type: string
          description: >-
            Financing cost (accrued interest) still owed by the borrower beyond
            the invoice amount.
        matchedChecks:
          type: object
      required:
        - id
        - invoiceId
        - route
        - amountAtoms
        - interestPortion
        - principalPortion
        - sellerPortion
        - remainingFinancingCost
        - matchedChecks
    DepositIntentDto:
      type: object
      properties: {}
