openapi: 3.1.0
info:
  title: Grid API
  description: |
    API for managing global payments on the open Money Grid. Built by Lightspark. See the full documentation at https://docs.lightspark.com/.
  version: '2025-10-13'
  contact:
    name: Lightspark Support
    email: support@lightspark.com
  license:
    name: Proprietary
    url: https://lightspark.com/terms
servers:
  - url: https://api.lightspark.com/grid/2025-10-13
    description: Production server
security:
  - BasicAuth: []
  - AgentAuth: []
tags:
  - name: Platform Configuration
    description: Platform configuration endpoints for managing global settings. You can also configure these settings in the Grid dashboard.
  - name: Customers
    description: Customer management endpoints for creating and updating customer information
  - name: Contact Verification
    description: Endpoints for verifying a customer's email and phone via one-time codes. Required only for customers whose payment provider mandates contact verification (e.g. EU customers); other providers return 409.
  - name: Strong Customer Authentication
    description: Endpoints for authorizing money-movement operations that require Strong Customer Authentication. Relevant only for customers in a region where SCA is required (e.g. EU); customers outside SCA-regulated regions never see an SCA challenge and these endpoints return 409.
  - name: KYC/KYB Verifications
    description: Endpoints for Know Your Customer (KYC) and Know Your Business (KYB) verification, including managing beneficial owners and triggering verification for customers.
  - name: Documents
    description: Endpoints for uploading and managing verification documents for customers and beneficial owners. Supports KYC and KYB document requirements.
  - name: Internal Accounts
    description: Internal account management endpoints for creating and managing internal accounts
  - name: Periodic Statements
    description: |
      Build, deliver, and evidence a Regulation E periodic statement for a customer's internal account. A statement period is a calendar month in US Central time (`America/Chicago`).

      A statement is issued for each customer's own USD internal account and covers their whole balance. Money received through a rule-based account appears on its owner's statement; rule-based, bulk settlement and platform-owned accounts have no statement of their own.

      **1. The statement — `GET /internal-accounts/{id}/balance-changes?startDate=&endDate=`**

      One row per change to the balance, in the order the money moved, with the opening and closing balances for the window in the same response. Page until `hasMore` is false, then assert this identity across every page before you render anything:

      ```
      openingBalance + Σ(data[].amount) == closingBalance
      ```

      If it does not hold, do not send the statement; contact support instead. `startDate` and `endDate` bound a half-open window `[startDate, endDate)`: for a monthly statement, pass the first instant of the month and the first instant of the following month, both in US Central time (for August 2026, `2026-08-01T00:00:00-05:00` and `2026-09-01T00:00:00-05:00`). Grid records a statement as fetched only for a window that is exactly one such month.

      **2. The receipt — `POST /internal-accounts/{id}/confirm-statement`**

      Once a month, after you have pulled and issued a period's statement, send a receipt with the `statementMonth` it covers. Grid stores it as the delivery record for that account and period. Sending it again is harmless: the first receipt's time is kept.

      **Timing**

      A window whose card settlement has not closed is refused with `409 NOT_YET_AVAILABLE` rather than answered with figures that could still change; retry once it has settled.
  - name: External Accounts
    description: External account management endpoints for creating and managing external bank accounts
  - name: Same-Currency Transfers
    description: Deprecated endpoints for transferring funds between internal and external accounts with the same currency. Use the quote endpoints under Cross-Currency Transfers instead, which now serve same-currency transfers as well.
  - name: Cross-Currency Transfers
    description: Endpoints for creating and confirming quotes for transfers, both same-currency and cross-currency
  - name: Transactions
    description: Endpoints for retrieving transaction information
  - name: Webhooks
    description: Webhook endpoints and configuration for receiving notifications
  - name: Invitations
    description: Endpoints for creating, claiming and managing UMA invitations
  - name: Sandbox
    description: Endpoints to trigger test cases in sandbox
  - name: API Tokens
    description: Endpoints to programmatically manage API tokens
  - name: Exchange Rates
    description: Endpoints for retrieving cached foreign exchange rates. Rates are cached for approximately 5 minutes and include platform-specific fees.
  - name: Discoveries
    description: Endpoints for discovering available payment rails, banks, and providers for a given country and currency corridor.
  - name: Embedded Wallet Auth
    description: Endpoints for registering and verifying end-user authentication credentials (email OTP, OAuth, passkey) used to sign Embedded Wallet actions.
  - name: Agent Management
    description: 'Endpoints for creating and managing agents (experimental), called by the partner''s backend using platform credentials. Covers the full agent lifecycle: creation, policy configuration, pausing, deletion, the device code installation flow, and approving or rejecting transactions initiated by agents.'
  - name: Agent Operations
    description: Endpoints called by the agent itself using its own credentials (obtained via device code redemption). Scoped to the agent's associated customer — all requests automatically operate on behalf of that customer and are subject to the agent's policy. When an action requires approval, the resulting transaction enters a pending state and must be approved by the platform via `POST /transactions/{transactionId}/approve`.
  - name: Cards
    description: Card management endpoints. Issue debit cards against an internal account, freeze / unfreeze, close, manage a card's funding source, and list card transactions.
  - name: Stablecoins
    description: Stablecoin issuance endpoints. Link provider accounts, register provider-created stablecoins, create direct mint/burn issuer operations, and track operation status.
paths:
  /config:
    get:
      summary: Get platform configuration
      description: Retrieve the current platform configuration
      operationId: getPlatformConfig
      tags:
        - Platform Configuration
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformConfig'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    patch:
      summary: Update platform configuration
      description: |-
        Update platform configuration settings. `cardConfigs` can establish platform-level caps on a single card transaction, on spend during one UTC calendar day, and on the number of transactions during one UTC calendar day. Grid enforces the lower of each platform cap and its corresponding card-specific value without replacing the card-specific value. Daily limits reset at 00:00 UTC.

        Set `cardConfigs.pinTargetOrigin` to the canonical HTTPS origin of your frontend before calling `GET /cards/{id}/pin-entry-url`. Omit the field to preserve its value or set it to `null` to clear it.
      operationId: updatePlatformConfig
      tags:
        - Platform Configuration
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlatformConfigUpdateRequest'
            example:
              umaDomain: mycompany.com
              webhookEndpoint: https://api.mycompany.com/webhooks/uma
              supportedCurrencies:
                - currencyCode: USD
                  minAmount: 100
                  maxAmount: 1000000
                  enabledTransactionTypes:
                    - OUTGOING
                    - INCOMING
                  requiredCounterpartyFields:
                    - name: FULL_NAME
                      mandatory: true
                    - name: NATIONALITY
                      mandatory: true
                    - name: BIRTH_DATE
                      mandatory: true
              embeddedWalletConfig:
                appName: Acme Wallet
                sendFromEmailAddress: noreply@acme.com
                sendFromEmailSenderName: Acme Notifications
                replyToEmailAddress: support@acme.com
                logoUrl: https://acme.com/logo.png
              cardTokenization2faConfig:
                displayName: Acme
                logoUrl: https://acme.com/card-email-logo.png
                email:
                  fromAddress: cards@acme.com
                  fromName: Acme Cards
                  replyToAddress: support@acme.com
                  subject: Your Acme card verification code
                  bodyText: Use this code to finish adding your Acme card to your digital wallet.
                sms:
                  templateSid: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  bodyText: Use this code to finish adding your Acme card to your digital wallet.
              cardConfigs:
                pinTargetOrigin: https://app.example.com
                maxSpendPerTransaction: 10000
                maxSpendPerDay: 50000
                maxTransactionsPerDay: 50
      responses:
        '200':
          description: Configuration updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformConfig'
        '400':
          description: Bad request. Includes `INVALID_INPUT` when `cardConfigs.pinTargetOrigin` is supplied but is neither null nor a canonical HTTPS origin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
  /exchange-rates:
    get:
      summary: Get exchange rates
      description: |
        Retrieve cached exchange rates for currency corridors. Returns FX rates that are cached
        for approximately 5 minutes. Rates include fees specific to your platform for authenticated requests.

        **Filtering Options:**
        - Filter by source currency to get all available destination corridors
        - Filter by specific destination currency or currencies
        - Provide a sending amount to get calculated receiving amounts
      operationId: getExchangeRates
      tags:
        - Exchange Rates
      security:
        - BasicAuth: []
      parameters:
        - name: sourceCurrency
          in: query
          description: Filter by source currency code (e.g., USD)
          required: false
          schema:
            type: string
          example: USD
        - name: destinationCurrency
          in: query
          description: Filter by destination currency code(s). Can be repeated for multiple currencies (e.g., &destinationCurrency=INR&destinationCurrency=GBP)
          required: false
          style: form
          explode: true
          schema:
            type: array
            items:
              type: string
          example:
            - INR
        - name: sendingAmount
          in: query
          description: Sending amount in the smallest unit of the source currency (e.g., cents for USD).  If no amount is provided, the default is 10000 in the sending currency smallest unit.
          required: false
          schema:
            type: integer
            format: int64
            exclusiveMinimum: 0
            default: 10000
          example: 10000
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExchangeRateListResponse'
              examples:
                allRatesFromUSD:
                  summary: All exchange rates from USD
                  value:
                    data:
                      - sourceCurrency:
                          code: USD
                          decimals: 2
                          name: US Dollar
                          symbol: $
                        sendingAmount: 10000
                        minSendingAmount: 100
                        maxSendingAmount: 10000000
                        destinationCurrency:
                          code: INR
                          decimals: 2
                          name: Indian Rupee
                          symbol: ₹
                        destinationPaymentRail: UPI
                        receivingAmount: 825000
                        exchangeRate: 0.012121
                        fees:
                          fixed: 100
                          total: 150
                        updatedAt: '2025-02-05T12:00:00Z'
                      - sourceCurrency:
                          code: USD
                          decimals: 2
                          name: US Dollar
                          symbol: $
                        sendingAmount: 10000
                        minSendingAmount: 100
                        maxSendingAmount: 10000000
                        destinationCurrency:
                          code: EUR
                          decimals: 2
                          name: Euro
                          symbol: €
                        destinationPaymentRail: SEPA_INSTANT
                        receivingAmount: 9250
                        exchangeRate: 1.081081
                        fees:
                          fixed: 10
                          total: 15
                        updatedAt: '2025-02-05T12:00:00Z'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /discoveries:
    get:
      summary: List available receiving institution names
      description: |
        Retrieve available payment institution names for a given country and currency. Use this endpoint
        to look up supported banks and payment providers for a specific corridor.
        If no country and currency parameter are provided, all payment institutions will be returned

        The `bankName` field in each result is the value to pass as `bankName` when
        creating an external account via `POST /customers/external-accounts`.
      operationId: getDiscoveries
      tags:
        - Discoveries
      security:
        - BasicAuth: []
      parameters:
        - name: country
          in: query
          description: ISO 3166-1 alpha-2 country code (e.g. PH)
          required: false
          schema:
            type: string
          example: PH
        - name: currency
          in: query
          description: ISO 4217 currency code (e.g. PHP)
          required: false
          schema:
            type: string
          example: PHP
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DiscoveryListResponse'
              examples:
                philippinesBanks:
                  summary: Payment rails for Philippines (PHP)
                  value:
                    data:
                      - bankName: Bank of the Philippine Islands (BPI)
                        displayName: Bank of the Philippine Islands (BPI)
                        country: PH
                        currency: PHP
                      - bankName: BDO Network Bank
                        displayName: BDO Network Bank / One Network Bank
                        country: PH
                        currency: PHP
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers:
    post:
      summary: Add a new customer
      description: Register a new customer in the system with an account identifier and bank account information
      operationId: createCustomer
      tags:
        - Customers
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerCreateRequestOneOf'
            examples:
              individualCustomer:
                summary: Create an individual customer
                value:
                  customerType: INDIVIDUAL
                  platformCustomerId: ind-9f84e0c2
                  region: US
                  currencies:
                    - USD
                    - USDC
                  fullName: Jane Smith
                  birthDate: '1990-01-15'
                  nationality: US
                  email: jane.smith@example.com
                  phoneNumber: '+14155551234'
              individualCustomerInferred:
                summary: Create an individual customer with inferred currencies
                value:
                  customerType: INDIVIDUAL
                  platformCustomerId: ind-7b3f1a9d
                  region: MX
                  fullName: Carlos García
                  birthDate: '1988-05-22'
                  nationality: MX
                  phoneNumber: '+525512345678'
              individualCustomerCurrenciesOnly:
                summary: Create an individual customer with currencies and no region
                value:
                  customerType: INDIVIDUAL
                  platformCustomerId: ind-4c2e8b15
                  currencies:
                    - BRL
                  fullName: Ana Oliveira
                  birthDate: '1992-11-03'
                  nationality: BR
                  phoneNumber: '+5511987654321'
              businessCustomer:
                summary: Create a business customer
                value:
                  customerType: BUSINESS
                  platformCustomerId: biz-acme-001
                  region: US
                  currencies:
                    - USD
                    - USDC
                  email: finance@acme.com
                  phoneNumber: '+14155559876'
                  businessInfo:
                    legalName: Acme Corporation
                    doingBusinessAs: Acme
                    country: US
                    registrationNumber: '5523041'
                    incorporatedOn: '2018-03-14'
                    entityType: LLC
                    taxId: 47-1234567
                    countriesOfOperation:
                      - US
                    businessType: INFORMATION
                    purposeOfAccount: CONTRACTOR_PAYOUTS
                    sourceOfFunds: Funds derived from customer payments for software services
                    expectedMonthlyTransactionCount: COUNT_100_TO_500
                    expectedMonthlyTransactionVolume: VOLUME_100K_TO_1M
                    expectedRecipientJurisdictions:
                      - US
                      - MX
                  address:
                    line1: 123 Market Street
                    line2: Suite 400
                    city: Seattle
                    state: WA
                    postalCode: '98101'
                    country: US
      responses:
        '200':
          description: The customer already created under this `Idempotency-Key`. A repeated request replays the original result and creates nothing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerOneOf'
        '201':
          description: Customer created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerOneOf'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '405':
          description: Method not allowed. Returned (as JSON, not HTML) when the request uses an HTTP method that is not supported on this path.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error405'
        '409':
          description: Conflict. `UMA_ADDRESS_EXISTS` when the requested UMA address is already taken; `CONFLICT` when `platformCustomerId` collides with an existing active customer on the same platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
    get:
      summary: List customers
      description: |
        Retrieve a list of customers with optional filtering parameters. Returns all customers that match
        the specified filters. If no filters are provided, returns all customers (paginated).
      operationId: listCustomers
      tags:
        - Customers
      security:
        - BasicAuth: []
      parameters:
        - name: platformCustomerId
          in: query
          description: Filter by platform-specific customer identifier
          required: false
          schema:
            type: string
        - name: customerType
          in: query
          description: Filter by customer type
          required: false
          schema:
            $ref: '#/components/schemas/CustomerType'
        - name: createdAfter
          in: query
          description: Filter customers created after this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: createdBefore
          in: query
          description: Filter customers created before this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: updatedAfter
          in: query
          description: Filter customers updated after this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: updatedBefore
          in: query
          description: Filter customers updated before this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
        - name: region
          in: query
          description: Filter by customer region (ISO 3166-1 alpha-2 country code)
          required: false
          schema:
            type: string
        - name: currency
          in: query
          description: Filter by currency code. Returns customers that have this currency in their enabled currencies list.
          required: false
          schema:
            type: string
        - name: umaAddress
          in: query
          description: Filter by uma address
          required: false
          schema:
            type: string
        - name: isIncludingDeleted
          in: query
          description: Whether to include deleted customers in the results. Default is false.
          required: false
          schema:
            type: boolean
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '405':
          description: Method not allowed. Returned (as JSON, not HTML) when the request uses an HTTP method that is not supported on this path.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error405'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/{customerId}:
    parameters:
      - name: customerId
        in: path
        description: System-generated unique customer identifier
        required: true
        schema:
          type: string
    get:
      summary: Get customer by ID
      description: Retrieve a customer by their system-generated ID
      operationId: getCustomerById
      tags:
        - Customers
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerOneOf'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    patch:
      summary: Update customer by ID
      description: |
        Update a customer's metadata by their system-generated ID.

        Most customer updates complete synchronously and return `200` with the updated customer. If the request changes `email` for a customer that has one or more tied Embedded Wallet internal accounts with `EMAIL_OTP` credentials, or changes `phoneNumber` for a customer that has one or more tied Embedded Wallet internal accounts with `SMS_OTP` credentials, the contact update uses the two-step signed-retry flow so the customer's wallet session authorizes the authentication credential update. On the signed retry, Grid updates the customer contact field and every tied matching OTP credential across all tied Embedded Wallets as one logical operation. If any tied credential cannot be updated, the customer contact field is not changed.

        Update `email` and `phoneNumber` in separate PATCH calls. A request that includes both fields is rejected.

        For an Embedded Wallet email or SMS auth phone update:

        1. Call `PATCH /customers/{customerId}` with the full update body and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. The pending challenge binds the submitted update fields and the set of tied Embedded Wallet OTP credentials that must be updated.

        2. Use the session API keypair of a verified authentication credential on one of the customer's tied Embedded Wallets to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated customer.
      operationId: updateCustomerById
      tags:
        - Customers
      security:
        - BasicAuth: []
      parameters:
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on one of the customer's tied Embedded Wallets. Required on the signed retry for Embedded Wallet email or SMS auth phone updates; ignored on the initial call and on customer updates that complete synchronously.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry for Embedded Wallet email or SMS auth phone updates; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: Request:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerUpdateRequestOneOf'
            examples:
              individualUpdate:
                summary: Update individual customer example
                value:
                  customerType: INDIVIDUAL
                  fullName: John Smith
                  birthDate: '1985-06-15'
                  currencies:
                    - USD
                    - EUR
                    - USDC
                  address:
                    line1: 456 Market St
                    city: Seattle
                    state: WA
                    postalCode: '98101'
                    country: US
              businessUpdate:
                summary: Update business customer example
                value:
                  customerType: BUSINESS
                  currencies:
                    - USD
                    - USDC
                  businessInfo:
                    legalName: New Tech Solutions LLC
                    registrationNumber: BRN-987654321
                    taxId: EIN-123456789
                  address:
                    line1: 100 Technology Parkway
                    city: Bellevue
                    state: WA
                    postalCode: '98004'
                    country: US
              embeddedWalletEmailUpdate:
                summary: Embedded Wallet email update request (both steps)
                value:
                  customerType: INDIVIDUAL
                  email: john.smith@example.com
              embeddedWalletPhoneUpdate:
                summary: Embedded Wallet SMS auth phone update request (both steps)
                value:
                  customerType: INDIVIDUAL
                  phoneNumber: '+14155559876'
              combinedContactUpdateRejected:
                summary: Combined email and phone update request (rejected)
                value:
                  customerType: INDIVIDUAL
                  email: john.smith@example.com
                  phoneNumber: '+14155559876'
      responses:
        '200':
          description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerUpdateResponse'
        '202':
          description: Challenge issued for an Embedded Wallet email or SMS auth phone update. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair from a verified authentication credential on one of the customer's tied Embedded Wallets, then retry the same request with `Grid-Wallet-Signature` and `Request-Id`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignedRequestChallenge'
              examples:
                embeddedWalletEmailUpdate:
                  summary: Embedded Wallet customer email update challenge
                  value:
                    payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"john.smith@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}'
                    requestId: Request:019542f5-b3e7-1d02-0000-000000000010
                    expiresAt: '2026-04-08T15:35:00Z'
                embeddedWalletPhoneUpdate:
                  summary: Embedded Wallet customer SMS auth phone update challenge
                  value:
                    payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155559876"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}'
                    requestId: Request:019542f5-b3e7-1d02-0000-000000000011
                    expiresAt: '2026-04-08T15:35:00Z'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Also returned for Embedded Wallet email or SMS auth phone update retries when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match the pending customer update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. Returned when the supplied email address is already associated with an `EMAIL_OTP` credential, or the supplied phone number is already associated with an `SMS_OTP` credential, on this or another internal account, or when the tied Embedded Wallet OTP credential set changed between the initial `202` challenge and the signed retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '424':
          description: Failed dependency. Returned when Grid cannot update one or more tied Embedded Wallet OTP credentials. The customer contact field is not changed unless all tied credentials are updated successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error424'
        '429':
          description: Too many requests. Returned with `RATE_LIMITED`.
          headers:
            Retry-After:
              description: Number of seconds to wait before retrying the request.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Delete customer by ID
      description: Delete a customer by their system-generated ID
      operationId: deleteCustomerById
      tags:
        - Customers
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Customer deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerOneOf'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '410':
          description: Customer deleted already
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error410'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/agreements:
    get:
      summary: List the current Grid agreement documents
      description: Retrieve the current version and Grid-hosted URL of every agreement Grid supports. Supply an entry's `version` as `termsVersion` when recording a customer's acceptance of that agreement. The list is a catalog of available agreements, not a list of the agreements a given customer is required to accept.
      operationId: listAgreementDocuments
      tags:
        - Customers
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Current agreement documents retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgreementDocumentListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
  /customers/end-user-terms:
    get:
      summary: Get the current Grid End User Terms
      description: Deprecated; use `GET /customers/agreements`, which lists every agreement Grid supports rather than the End User Terms alone. This operation keeps its original single-document response, so existing integrations continue to work unchanged.
      operationId: getEndUserTerms
      deprecated: true
      tags:
        - Customers
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Current End User Terms retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndUserTerms'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
  /customers/{customerId}/kyc-link:
    parameters:
      - name: customerId
        in: path
        description: The Grid customer ID to generate a KYC link for.
        required: true
        schema:
          type: string
    post:
      summary: Generate a hosted KYC/KYB link for an existing customer
      description: |
        Generate a single-use hosted URL the customer can complete to verify their identity, and (where supported) a provider-specific `token` for embedding the verification flow directly via the provider's SDK.

        The customer must already exist — create them with `POST /customers` first. Calling this endpoint does not change the customer's verification status; the customer remains at their current status until they complete (or fail) the hosted flow.

        This endpoint generates the link for both customer types; `customerType` selects which flow the provider runs. `INDIVIDUAL` runs identity verification (KYC), tracked on `kycStatus`. `BUSINESS` runs business verification (KYB), tracked on `kybStatus` — the flow confirms the company details, collects formation, ownership, and proof-of-address documents, and gathers the control person and every beneficial owner holding 25% or more. Business information already supplied via `POST /customers` or `PATCH /customers/{customerId}` is prefilled, so send what you have before generating the link.

        The hosted link is one of two ways to verify a customer. To collect the data yourself instead, submit it through `POST /customers`, `POST /beneficial-owners` (business customers), and `POST /documents`, then call `POST /verifications`. Both paths produce the same status transitions and the same `CUSTOMER.KYC_*` / `CUSTOMER.KYB_*` webhooks.

        Each call returns a fresh link. Previously-issued links are not invalidated, but they remain single-use and will expire on their own.
      operationId: createCustomerKycLink
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KycLinkCreateRequest'
      responses:
        '201':
          description: KYC link generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KycLinkResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: The customer's payment provider requires contact verification and the customer's email and phone are not both `VERIFIED` yet. Complete contact verification (see `verify-email` and `verify-phone`) before generating a KYC link.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/{customerId}/verify-email:
    parameters:
      - name: customerId
        in: path
        description: The Grid customer ID to send an email verification code to.
        required: true
        schema:
          type: string
    post:
      summary: Send an email verification code to a customer
      description: |
        Send (or resend) a one-time code to the customer's email address to begin
        email verification.

        This endpoint is only meaningful for customers whose payment provider
        requires contact verification (e.g. EU customers); the customer's
        `contactVerification.email` status reflects progress. For customers whose
        provider has no such requirement, this returns `409`.

        Call `POST /customers/{customerId}/verify-email/confirm` with the received
        code to complete verification. Re-POST this endpoint to resend the code.

        In sandbox, no email is sent and the code is always `123456`.
      operationId: sendCustomerEmailVerification
      tags:
        - Contact Verification
      security:
        - BasicAuth: []
      responses:
        '204':
          description: Verification code sent.
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: The customer's payment provider does not require contact verification, or the email is already verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/{customerId}/verify-email/confirm:
    parameters:
      - name: customerId
        in: path
        description: The Grid customer ID whose email is being verified.
        required: true
        schema:
          type: string
    post:
      summary: Confirm a customer's email verification code
      description: |
        Submit the one-time code the customer received by email to mark their email
        as `VERIFIED`. On success, `contactVerification.email` becomes `VERIFIED`.

        Both email and phone must be `VERIFIED` before the customer can begin KYC.

        In sandbox, the code is always `123456`.
      operationId: confirmCustomerEmailVerification
      tags:
        - Contact Verification
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactVerificationConfirmRequest'
      responses:
        '200':
          description: Email verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerOneOf'
        '400':
          description: Invalid or expired code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: The customer's payment provider does not require contact verification, or the email is already verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/{customerId}/verify-phone:
    parameters:
      - name: customerId
        in: path
        description: The Grid customer ID to send a phone verification code to.
        required: true
        schema:
          type: string
    post:
      summary: Send a phone verification code to a customer
      description: |
        Send (or resend) a one-time code via SMS to the customer's phone number to
        begin phone verification.

        This endpoint is only meaningful for customers whose payment provider
        requires contact verification (e.g. EU customers); the customer's
        `contactVerification.phone` status reflects progress. For customers whose
        provider has no such requirement, this returns `409`.

        Call `POST /customers/{customerId}/verify-phone/confirm` with the received
        code to complete verification. Re-POST this endpoint to resend the code.

        In sandbox, no SMS is sent and the code is always `123456`.
      operationId: sendCustomerPhoneVerification
      tags:
        - Contact Verification
      security:
        - BasicAuth: []
      responses:
        '204':
          description: Verification code sent.
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: The customer's payment provider does not require contact verification, or the phone is already verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/{customerId}/verify-phone/confirm:
    parameters:
      - name: customerId
        in: path
        description: The Grid customer ID whose phone is being verified.
        required: true
        schema:
          type: string
    post:
      summary: Confirm a customer's phone verification code
      description: |
        Submit the one-time code the customer received via SMS to mark their phone
        as `VERIFIED`. On success, `contactVerification.phone` becomes `VERIFIED`.

        Both email and phone must be `VERIFIED` before the customer can begin KYC.

        In sandbox, the code is always `123456`.
      operationId: confirmCustomerPhoneVerification
      tags:
        - Contact Verification
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactVerificationConfirmRequest'
      responses:
        '200':
          description: Phone verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CustomerOneOf'
        '400':
          description: Invalid or expired code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: The customer's payment provider does not require contact verification, or the phone is already verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/factors:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer whose factors are listed or enrolled.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
    get:
      summary: List enrolled SCA factors
      description: |
        List the Strong Customer Authentication factors the customer has enrolled.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: listScaFactors
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      responses:
        '200':
          description: The customer's enrolled SCA factors.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScaFactorList'
        '400':
          description: The authentication provider rejected the request as caller input, for example a malformed factor or reset reference.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    post:
      summary: Start SCA factor enrollment
      description: |
        Begin enrolling an SCA factor for the customer. Enrollment covers the
        explicit, opt-in factors a customer chooses to add — the request body's
        `type` selects `TOTP` or `PASSKEY`. Returns the factor-specific material
        needed to finish via `POST /sca/factors/confirm`.

        `SMS_OTP` is implicit and is not enrolled through this endpoint. Every
        customer in an SCA-regulated region has a verified phone number from
        customer creation (via the Contact Verification flows —
        `POST /customers/{customerId}/verify-phone` and `.../verify-phone/confirm`),
        so SMS is always available as a factor with no extra setup and appears
        among the customer's enrolled factors in `GET /sca/factors`.

        A customer may have **only one passkey**. Starting a passkey enrollment when
        one is already enrolled returns `409` (`PASSKEY_ALREADY_ENROLLED`) — delete it
        via `DELETE /sca/factors/{credentialId}` first.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: startScaFactorEnrollment
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScaFactorEnrollRequestOneOf'
      responses:
        '200':
          description: Enrollment started; the factor-specific completion material is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScaFactorEnrollStartOneOf'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer (`CONFLICT`), or a passkey enrollment was requested while one is already enrolled (`PASSKEY_ALREADY_ENROLLED`) — only one passkey per customer is supported.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/factors/confirm:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer confirming a factor enrollment.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
    post:
      summary: Confirm SCA factor enrollment
      description: |
        Finalize the factor enrollment started by `POST /sca/factors`. The request
        body is discriminated by `type`: for `TOTP`, submit the shared `secret` from
        the start call plus the first `code`; for `PASSKEY`, submit the WebAuthn
        `credential` the device produced plus the `origin` it was produced against.
        The threaded secret/credential binds the confirmation to its enrollment, so
        no separate id is needed.

        A TOTP confirmation returns one-time recovery codes (shown once); a passkey
        confirmation returns the enrolled factor.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.

        In sandbox, the TOTP code is always `123456`.
      operationId: confirmScaFactorEnrollment
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScaFactorConfirmRequestOneOf'
      responses:
        '200':
          description: Factor enrolled; the factor-specific result is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScaFactorConfirmResponseOneOf'
        '400':
          description: Invalid or incorrect confirmation proof
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/factors/{credentialId}:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer whose factor is being deleted.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
      - name: credentialId
        in: path
        description: The credential id of the enrolled factor to delete (from the factor's `credentialId`).
        required: true
        schema:
          type: string
    delete:
      summary: Delete an enrolled SCA factor
      description: |
        Delete an enrolled SCA factor by its credential id. Today only `PASSKEY`
        factors carry a `credentialId` and are deletable this way.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: deleteScaFactor
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      responses:
        '204':
          description: Factor deleted; no content is returned.
        '400':
          description: The authentication provider rejected the request as caller input, for example a malformed factor or reset reference.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or factor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/login/start:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer starting an SCA login.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
    post:
      summary: Start an SCA login
      description: |
        Begin an SCA login for the customer with the chosen factor, opening the
        end-user SCA session (an exemption gating read / account access beyond the
        per-transaction window). Returns factor-specific material: `SMS_OTP`
        dispatches a code and returns a `challengeId` + `expiresAt`; `TOTP` returns
        only the factor (the customer reads the code from their app); `PASSKEY`
        returns WebAuthn `passkeyOptions`. Complete with
        `POST /sca/login/complete`.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: startScaLogin
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScaLoginStartRequest'
      responses:
        '200':
          description: SCA login started; factor-specific material is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScaLoginStart'
        '400':
          description: Invalid or unknown factor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/login/complete:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer completing an SCA login.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
    post:
      summary: Complete an SCA login
      description: |
        Finalize an SCA login by submitting the proof for the started factor
        (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for
        `PASSKEY`), echoing the `challengeId` for `SMS_OTP`. Returns the
        reported session status.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.

        In sandbox, the SMS/TOTP code is always `123456`.
      operationId: completeScaLogin
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScaLoginCompleteRequest'
      responses:
        '200':
          description: SCA login completed; the session status is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScaLoginComplete'
        '400':
          description: Invalid or expired proof
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '423':
          description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error423'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/record-event:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer the security event is recorded for.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
    post:
      summary: Record a security event
      description: |
        Record a client-side security-relevant event for the customer with Grid's risk engine (e.g. a sign-in, a sensitive view), to
        feed adaptive-authentication signals.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: recordSecurityEvent
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSecurityEventRequest'
      responses:
        '200':
          description: Event recorded; the customer's resulting login-security state is returned (including any lockout).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordSecurityEventResponse'
        '400':
          description: Invalid event type
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '423':
          description: The customer's login is locked (or suspended) after too many failed attempts. `details.lockedUntil` says when they may retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error423'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/factors/reset:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer resetting a factor.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
    post:
      summary: Start a 2FA reset
      description: |
        Begin recovering a lost enrolled factor via a liveness-gated, poll-based
        flow. Opens the liveness check and returns a `resetId` plus the
        opaque liveness handles (`livenessAccessToken` / `verificationLink`) the end
        user completes it with. Poll
        `GET /sca/factors/reset/{resetId}` until liveness
        passes, then call the complete endpoint.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: startTwoFactorReset
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TwoFactorResetStartRequest'
      responses:
        '201':
          description: Reset initiated; the reset handle and liveness material are returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TwoFactorResetStart'
        '400':
          description: Invalid or unknown factor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '429':
          description: Too many reset attempts. Reset initiation is rate-limited to 5 per 24 hours per customer; retry after the window indicated by `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/factors/reset/{resetId}:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer whose reset status is polled.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
      - name: resetId
        in: path
        description: The reset handle returned by the start call.
        required: true
        schema:
          type: string
    get:
      summary: Get 2FA reset status
      description: |
        Poll the status of an in-progress 2FA reset until it reaches the liveness-passed value, after which the reset can be completed.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: getTwoFactorResetStatus
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      responses:
        '200':
          description: The current reset status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TwoFactorResetStatus'
        '400':
          description: The authentication provider rejected the request as caller input, for example a malformed factor or reset reference.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or reset not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sca/factors/reset/{resetId}/complete:
    parameters:
      - name: customerId
        in: query
        description: The unique identifier of the customer completing the reset.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
      - name: resetId
        in: path
        description: The reset handle returned by the start call.
        required: true
        schema:
          type: string
    post:
      summary: Complete a 2FA reset
      description: |
        Complete a 2FA reset once liveness has passed, clearing the lost factor so
        the customer can re-enroll.

        The body is required and must carry the end user's `endUserIpAddress`. For an
        `SMS_OTP` reset, also supply the new `mobile` number — completing the reset
        enrolls it as the customer's number; other factors need no `mobile`.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: completeTwoFactorReset
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TwoFactorResetCompleteRequest'
      responses:
        '204':
          description: Reset completed; no content is returned.
        '400':
          description: Reset not ready (liveness not yet passed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or reset not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/external-accounts/{externalAccountId}/trust:
    parameters:
      - name: externalAccountId
        in: path
        description: The unique identifier of the external account (beneficiary) being trusted.
        required: true
        schema:
          type: string
    post:
      summary: Start trusting a beneficiary
      description: |
        Begin trusting (whitelisting) an external account so future sends to it can
        skip the per-transaction SCA ceremony. Returns the `scaChallenge` to satisfy
        (when one is issued). Complete with
        `POST /customers/external-accounts/{externalAccountId}/trust/confirm`.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: startBeneficiaryTrust
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Beneficiary trust started; the SCA challenge (if any) is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficiaryTrustStart'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or external account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/external-accounts/{externalAccountId}/trust/confirm:
    parameters:
      - name: externalAccountId
        in: path
        description: The unique identifier of the external account (beneficiary) being trusted.
        required: true
        schema:
          type: string
    post:
      summary: Confirm trusting a beneficiary
      description: |
        Finalize trusting a beneficiary (identified by the `externalAccountId` in the
        path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or
        `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when
        one was issued. Returns `trusted: true`.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.

        In sandbox, the SMS/TOTP code is always `123456`.
      operationId: confirmBeneficiaryTrust
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest'
      responses:
        '200':
          description: Beneficiary trusted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficiaryTrustConfirm'
        '400':
          description: Invalid or expired proof
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or external account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/external-accounts/{externalAccountId}/untrust:
    parameters:
      - name: externalAccountId
        in: path
        description: The unique identifier of the external account (beneficiary) being untrusted.
        required: true
        schema:
          type: string
    post:
      summary: Start untrusting a beneficiary
      description: |
        Begin untrusting (removing the trusted mark from) an external account, so
        future sends to it are dynamically linked and require the per-transaction SCA
        ceremony again. Returns the `scaChallenge` to satisfy (when one is issued).
        Complete with
        `POST /customers/external-accounts/{externalAccountId}/untrust/confirm`.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.
      operationId: startBeneficiaryUntrust
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Beneficiary untrust started; the SCA challenge (if any) is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficiaryTrustStart'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or external account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/external-accounts/{externalAccountId}/untrust/confirm:
    parameters:
      - name: externalAccountId
        in: path
        description: The unique identifier of the external account (beneficiary) being untrusted.
        required: true
        schema:
          type: string
    post:
      summary: Confirm untrusting a beneficiary
      description: |
        Finalize untrusting a beneficiary (identified by the `externalAccountId` in
        the path) by submitting the SCA proof (`code` for `SMS_OTP` / `TOTP`, or
        `passkeyAssertion` + `origin` for `PASSKEY`), echoing the `challengeId` when
        one was issued. Returns `trusted: false`.

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.

        In sandbox, the SMS/TOTP code is always `123456`.
      operationId: confirmBeneficiaryUntrust
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BeneficiaryTrustConfirmRequest'
      responses:
        '200':
          description: Beneficiary untrusted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficiaryTrustConfirm'
        '400':
          description: Invalid or expired proof
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or external account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /internal-accounts:
    post:
      summary: Create an internal account
      description: |
        Create a rule-based internal account.

        A rule-based account is an additional account number for an account holder
        that already holds an account in the same currency. It carries a routing
        rule: every payment that settles into it is attributed to that account
        number and then swept to the rule's destination, so you can issue one number
        per payer and reconcile incoming payments without matching on remittance
        text.

        Only `RULE_BASED` accounts can be created here. `INTERNAL_FIAT`,
        `INTERNAL_CRYPTO`, and `EMBEDDED_WALLET` accounts are provisioned
        automatically when an account holder is created or approved.

        The response carries the account's own `fundingPaymentInstructions` — the
        routable number payers send to. Rule-based accounts are currently available
        in `USD`, for a customer or for your platform itself.

        Sandbox platforms can create rule-based accounts and exercise the whole
        flow. The account number is generated locally rather than issued by a
        partner bank, and `POST /sandbox/internal-accounts/{accountId}/fund` stands
        in for a settled deposit — funding the account triggers its sweep just as a
        real deposit would.

        Send an `Idempotency-Key` header to make retries safe: a request carrying a
        key already used returns the account created by the first request with a
        `200` instead of a `201`. Reusing a key for a materially different account
        is rejected with `409`. Without a key a retry cannot be recognised, so each
        request creates its own account and mints its own account number.
      operationId: createInternalAccount
      tags:
        - Internal Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          description: |
            A unique identifier for the request. Strongly recommended: creating the account mints an account number that cannot be reversed, and the key is what lets a retry return the first account rather than minting a second.
          required: false
          schema:
            type: string
            maxLength: 255
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InternalAccountCreateRequest'
            examples:
              externalDestination:
                summary: Forward every payment to an external account over ACH
                value:
                  customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                  type: RULE_BASED
                  currency: USD
                  label: invoice-4417
                  sweepRule:
                    destination:
                      accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                      paymentRail: ACH
                    purposeOfPayment: SELF
                    description: Rent sweep
                    remittanceInformation: Unit 4B March
              internalDestination:
                summary: Forward every payment to another internal account
                value:
                  customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                  type: RULE_BASED
                  currency: USD
                  label: payer-northwind
                  sweepRule:
                    destination:
                      accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
              onBehalfOfCustomer:
                summary: A platform account that receives payments for a customer
                value:
                  onBehalfOfCustomerId: Customer:019542f5-b3e7-1d02-0000-000000000002
                  type: RULE_BASED
                  currency: USD
                  label: customer-0002-collections
                  sweepRule:
                    destination:
                      accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
      responses:
        '200':
          description: The `Idempotency-Key` was already used to create this account. The account created by the first request is returned unchanged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalAccount'
        '201':
          description: Internal account created. `fundingPaymentInstructions` carries the account's own number.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalAccount'
        '400':
          description: Bad request. Returned when `type` is not `RULE_BASED`, when `sweepRule` is missing, when the currency is not supported for rule-based accounts, when the account holder has no account in that currency yet, when the destination cannot accept the requested payment rail, and for general invalid parameters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - rule-based accounts are not enabled for this platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Customer or destination account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - the `Idempotency-Key` was already used to create a materially different account. Use a new key for a new account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/internal-accounts:
    get:
      summary: List Customer internal accounts
      description: |
        Retrieve a list of internal accounts with optional filtering parameters. Returns all
        internal accounts that match the specified filters. If no filters are provided, returns all internal accounts
        (paginated).

        Internal accounts are created automatically when a customer is created based on the platform configuration.
      operationId: listCustomerInternalAccounts
      tags:
        - Internal Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: currency
          in: query
          description: Filter by currency code
          required: false
          schema:
            type: string
        - name: customerId
          in: query
          description: Filter by internal accounts associated with a specific customer
          required: false
          schema:
            type: string
        - name: type
          in: query
          description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts, or `RULE_BASED` for the additional account numbers issued for a customer.
          required: false
          schema:
            $ref: '#/components/schemas/InternalAccountType'
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalAccountListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /platform/internal-accounts:
    get:
      summary: List platform internal accounts
      description: |
        Retrieve a list of all internal accounts that belong to the platform, as opposed to an individual customer.

        These accounts are created automatically when the platform is configured for each supported currency. They can be used for things like distributing bitcoin rewards to customers, or for other platform-wide purposes.
      operationId: listPlatformInternalAccounts
      tags:
        - Internal Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: currency
          in: query
          description: Filter by currency code
          required: false
          schema:
            type: string
        - name: type
          in: query
          description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for a customer, `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for the platform-managed holding accounts, `RULE_BASED` for the additional account numbers issued with a sweep rule, or `BULK_SETTLEMENT` for the account that receives bulk settlements.
          required: false
          schema:
            $ref: '#/components/schemas/InternalAccountType'
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlatformInternalAccountListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/external-accounts:
    get:
      summary: List Customer external accounts
      description: |
        Retrieve a list of external accounts with optional filtering parameters. Returns all
        external accounts that match the specified filters. If no filters are provided, returns all external accounts
        (paginated).

        External accounts are bank accounts, cryptocurrency wallets, or other payment destinations that customers can use to receive funds from the platform.
      operationId: listCustomerExternalAccounts
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: currency
          in: query
          description: Filter by currency code
          required: false
          schema:
            type: string
        - name: customerId
          in: query
          description: Filter by external accounts associated with a specific customer
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccountListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    post:
      summary: Add a new external account
      description: Register a new external bank account for a customer.
      operationId: createCustomerExternalAccount
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerExternalAccountCreateRequest'
            examples:
              usBankAccount:
                summary: Create external US bank account
                value:
                  customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                  currency: USD
                  accountInfo:
                    accountType: USD_ACCOUNT
                    accountNumber: '12345678901'
                    routingNumber: '123456789'
                    bankAccountType: CHECKING
                    bankName: Chase Bank
                    platformAccountId: ext_acc_123456
                    beneficiary:
                      beneficiaryType: INDIVIDUAL
                      fullName: John Doe
                      birthDate: '1990-01-15'
                      nationality: US
                      address:
                        line1: 123 Main Street
                        city: San Francisco
                        state: CA
                        postalCode: '94105'
                        country: US
              sparkWallet:
                summary: Create external Spark wallet
                value:
                  customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                  currency: BTC
                  accountInfo:
                    accountType: SPARK_WALLET
                    address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu
      responses:
        '201':
          description: External account created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccount'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - External account already exists. When the conflicting account belongs to the customer in `customerId` (a duplicate wallet) or to any account on your platform (a duplicate `platformAccountId`), `details` carries its `externalAccountId` and `platformAccountId` so a retried request can recover it. `details` is omitted when the conflicting account cannot be identified, for example when the `platformAccountId` is already used on another platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
              examples:
                duplicateExternalAccount:
                  summary: Duplicate external account
                  value:
                    code: DUPLICATE_EXTERNAL_ACCOUNT
                    reason: An external account with this platform_account_id already exists
                    details:
                      externalAccountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                      platformAccountId: acme-acct-42
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/external-accounts/{externalAccountId}:
    parameters:
      - name: externalAccountId
        in: path
        description: System-generated unique external account identifier
        required: true
        schema:
          type: string
    get:
      summary: Get customer external account by ID
      description: Retrieve a customer external account by its system-generated ID
      operationId: getCustomerExternalAccountById
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccount'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Delete customer external account by ID
      description: |-
        Delete a customer external account by its system-generated ID.
        An account that is currently a trusted beneficiary for SCA cannot be deleted — untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete.
      operationId: deleteCustomerExternalAccountById
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      responses:
        '204':
          description: External account deleted successfully
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Returned with `BENEFICIARY_TRUSTED` when the external account is currently a trusted beneficiary. Untrust it first, then delete.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/external-accounts/{externalAccountId}/challenge:
    post:
      summary: Start an ownership verification challenge
      description: |
        Start (or restart) ownership verification for a `FIRST_PARTY` self-custody
        crypto wallet external account in `PENDING_OWNERSHIP_VERIFICATION` or
        `UNVERIFIED` status. The response carries the method-specific challenge
        material:

        - `WALLET_SIGNATURE` — a `messageToSign`; have the wallet sign it exactly
          and submit the result to the verify endpoint to complete verification
          synchronously.
        - `LIVENESS` — a hosted `verificationLink` (and possibly an embed
          `token`); the user completes a biometric flow and verification completes
          asynchronously. The outcome is delivered via
          `EXTERNAL_ACCOUNT.STATUS_UPDATED` webhooks or by polling the account.

        Calling this endpoint again abandons any in-flight challenge and issues a
        new one with the requested method — use it to retry after a failed
        attempt, to replace an expired challenge, or to switch methods. An
        `UNVERIFIED` account returns to `PENDING_OWNERSHIP_VERIFICATION` when a
        new challenge is issued.

        Completing ownership verification moves the account to `ACTIVE`.
      operationId: createExternalAccountOwnershipChallenge
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: externalAccountId
          in: path
          description: External account ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OwnershipChallengeRequest'
      responses:
        '201':
          description: Challenge created; the method-specific material is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OwnershipChallenge'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Ownership verification is not applicable to this external account (not a self-custody crypto wallet, not `FIRST_PARTY`, or already verified).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/external-accounts/{externalAccountId}/verify:
    post:
      summary: Verify ownership with a wallet signature
      description: |
        Complete a `WALLET_SIGNATURE` challenge by submitting the signature the
        wallet produced for the challenge's `messageToSign`. The message must be
        signed exactly as returned, and the signature must be submitted before
        the challenge's `expiresAt` — after expiry, start a new challenge.

        On success the account moves to `ACTIVE`; on an invalid signature it
        moves to `UNVERIFIED` (start a new challenge to retry). `LIVENESS`
        challenges complete asynchronously and never use this endpoint — their
        outcome is delivered via `EXTERNAL_ACCOUNT.STATUS_UPDATED` webhooks or by
        polling the account.
      operationId: verifyExternalAccountOwnership
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: externalAccountId
          in: path
          description: External account ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OwnershipVerifyRequest'
      responses:
        '200':
          description: 'Signature valid; the updated external account is returned with `status: ACTIVE`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccount'
        '400':
          description: Invalid or expired signature. The account moves to `UNVERIFIED`; start a new challenge to retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: No `WALLET_SIGNATURE` challenge is outstanding for this external account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /platform/external-accounts:
    get:
      summary: List platform external accounts
      description: |
        Retrieve a list of all external accounts that belong to the platform, as opposed to an individual customer.

        These accounts are used for platform-wide operations such as receiving funds from external sources or managing platform-level payment destinations.
      operationId: listPlatformExternalAccounts
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: currency
          in: query
          description: Filter by currency code
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccountListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    post:
      summary: Add a new platform external account
      description: Register a new external bank account for the platform.
      operationId: createPlatformExternalAccount
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlatformExternalAccountCreateRequest'
            examples:
              usBankAccount:
                summary: Create external US bank account
                value:
                  currency: USD
                  accountInfo:
                    accountType: USD_ACCOUNT
                    accountNumber: '12345678901'
                    routingNumber: '123456789'
                    bankAccountType: CHECKING
                    bankName: Chase Bank
                    platformAccountId: ext_acc_123456
                    beneficiary:
                      beneficiaryType: INDIVIDUAL
                      fullName: John Doe
                      birthDate: '1990-01-15'
                      nationality: US
                      address:
                        line1: 123 Main Street
                        city: San Francisco
                        state: CA
                        postalCode: '94105'
                        country: US
              sparkWallet:
                summary: Create external Spark wallet
                value:
                  currency: BTC
                  accountInfo:
                    accountType: SPARK_WALLET
                    address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu
      responses:
        '201':
          description: External account created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccount'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - External account already exists. When the conflicting account belongs to your platform, `details` carries its `externalAccountId` and `platformAccountId` so a retried request can recover it. `details` is omitted when the conflicting account cannot be identified, for example when the `platformAccountId` is already used on another platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
              examples:
                duplicateExternalAccount:
                  summary: Duplicate external account
                  value:
                    code: DUPLICATE_EXTERNAL_ACCOUNT
                    reason: An external account with this platform_account_id already exists
                    details:
                      externalAccountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                      platformAccountId: acme-settlement-usdc
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /platform/external-accounts/{externalAccountId}:
    parameters:
      - name: externalAccountId
        in: path
        description: System-generated unique external account identifier
        required: true
        schema:
          type: string
    get:
      summary: Get platform external account by ID
      description: Retrieve a platform external account by its system-generated ID
      operationId: getPlatformExternalAccountById
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccount'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Delete platform external account by ID
      description: Delete a platform external account by its system-generated ID
      operationId: deletePlatformExternalAccountById
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      responses:
        '204':
          description: External account deleted successfully
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /platform/external-accounts/{externalAccountId}/challenge:
    post:
      summary: Start an ownership verification challenge
      description: |
        Start (or restart) ownership verification for a `FIRST_PARTY` self-custody
        crypto wallet external account in `PENDING_OWNERSHIP_VERIFICATION` or
        `UNVERIFIED` status. The response carries the method-specific challenge
        material:

        - `WALLET_SIGNATURE` — a `messageToSign`; have the wallet sign it exactly
          and submit the result to the verify endpoint to complete verification
          synchronously.
        - `LIVENESS` — a hosted `verificationLink` (and possibly an embed
          `token`); the user completes a biometric flow and verification completes
          asynchronously. The outcome is delivered via
          `EXTERNAL_ACCOUNT.STATUS_UPDATED` webhooks or by polling the account.

        Calling this endpoint again abandons any in-flight challenge and issues a
        new one with the requested method — use it to retry after a failed
        attempt, to replace an expired challenge, or to switch methods. An
        `UNVERIFIED` account returns to `PENDING_OWNERSHIP_VERIFICATION` when a
        new challenge is issued.

        Completing ownership verification moves the account to `ACTIVE`.
      operationId: createPlatformExternalAccountOwnershipChallenge
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: externalAccountId
          in: path
          description: External account ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OwnershipChallengeRequest'
      responses:
        '201':
          description: Challenge created; the method-specific material is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OwnershipChallenge'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Ownership verification is not applicable to this external account (not a self-custody crypto wallet, not `FIRST_PARTY`, or already verified).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /platform/external-accounts/{externalAccountId}/verify:
    post:
      summary: Verify ownership with a wallet signature
      description: |
        Complete a `WALLET_SIGNATURE` challenge by submitting the signature the
        wallet produced for the challenge's `messageToSign`. The message must be
        signed exactly as returned, and the signature must be submitted before
        the challenge's `expiresAt` — after expiry, start a new challenge.

        On success the account moves to `ACTIVE`; on an invalid signature it
        moves to `UNVERIFIED` (start a new challenge to retry). `LIVENESS`
        challenges complete asynchronously and never use this endpoint — their
        outcome is delivered via `EXTERNAL_ACCOUNT.STATUS_UPDATED` webhooks or by
        polling the account.
      operationId: verifyPlatformExternalAccountOwnership
      tags:
        - External Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: externalAccountId
          in: path
          description: External account ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OwnershipVerifyRequest'
      responses:
        '200':
          description: 'Signature valid; the updated external account is returned with `status: ACTIVE`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccount'
        '400':
          description: Invalid or expired signature. The account moves to `UNVERIFIED`; start a new challenge to retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: No `WALLET_SIGNATURE` challenge is outstanding for this external account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /beneficial-owners:
    post:
      summary: Create a beneficial owner
      description: |
        Add a beneficial owner, director, or company officer to a business customer. The beneficial owner will go through KYC verification automatically.
      operationId: createBeneficialOwner
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BeneficialOwnerCreateRequest'
      responses:
        '201':
          description: Beneficial owner created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficialOwner'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List beneficial owners
      description: |
        Retrieve a list of beneficial owners for a business customer.
      operationId: listBeneficialOwners
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      parameters:
        - name: customerId
          in: query
          description: The business customer ID
          required: true
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficialOwnerListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /beneficial-owners/{beneficialOwnerId}:
    get:
      summary: Get a beneficial owner
      description: Retrieve details of a specific beneficial owner by ID.
      operationId: getBeneficialOwner
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      parameters:
        - name: beneficialOwnerId
          in: path
          description: Beneficial owner ID
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficialOwner'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Beneficial owner not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    patch:
      summary: Update a beneficial owner
      description: Update details of a specific beneficial owner. Only provided fields are updated.
      operationId: updateBeneficialOwner
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      parameters:
        - name: beneficialOwnerId
          in: path
          description: Beneficial owner ID
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BeneficialOwnerUpdateRequest'
      responses:
        '200':
          description: Beneficial owner updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeneficialOwner'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Beneficial owner not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Delete a beneficial owner
      description: Remove a beneficial owner from a business customer. The owner's uploaded documents are deleted with them, and the customer's verification status is recomputed without this owner. The request is rejected while a verification review that includes this owner is in progress; wait for the review to finish and retry.
      operationId: deleteBeneficialOwner
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      parameters:
        - name: beneficialOwnerId
          in: path
          description: Beneficial owner ID
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Beneficial owner deleted successfully
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Beneficial owner not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. A verification review that includes this beneficial owner is in progress. Retry after the review finishes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /documents:
    post:
      summary: Upload a document
      description: |
        Upload a verification document for a customer or beneficial owner.
        The request must use multipart/form-data with the file in the `file` field
        and metadata in the remaining fields.

        Every upload requires `file`, `documentType`, `country`, and
        `documentHolder`. Identity documents require more fields:

        | `documentType` | Also required | `side` |
        | -------------- | ------------- | ------ |
        | `PASSPORT` | `documentNumber`, `issuingAuthority` | Not allowed |
        | `DRIVERS_LICENSE` | `documentNumber`, `issuingAuthority` | Required |
        | `NATIONAL_ID` | `documentNumber`, `issuingAuthority` | Required |
        | Any other type | None | Optional |

        Where `side` is required, upload the `FRONT` and `BACK` as two separate
        documents. A driver's license issued in Brazil (`country` is `BR`) is
        the exception: upload it as one image and omit `side`.

        A missing required field, or a `side` that isn't allowed, returns
        `400 INVALID_INPUT`.

        Supported file types: `application/pdf`, `image/jpeg`, and `image/png`.
        Grid matches on the `Content-Type` of the multipart part, not the file
        extension. Any other type, and any file over 10 MB, returns
        `400 INVALID_INPUT`.

        Grid forwards the file to its verification provider, which screens it as
        the request is handled and can reject it with `422 DOCUMENT_REJECTED`.
        To pass that screen, a photo or scan of a document must:

        - show the whole document, with all four corners inside the frame and
          nothing overlapping an edge
        - be in focus and free of glare, so every field and the machine-readable
          zone can be read
        - be in color, not a black-and-white copy
        - be a photo or scan of the physical document, not a screen capture, and
          not retouched in an image editor
        - be unexpired
      operationId: uploadDocument
      tags:
        - Documents
      security:
        - BasicAuth: []
      requestBody:
        $ref: '#/components/requestBodies/DocumentUploadRequestBody'
      responses:
        '201':
          description: Document uploaded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Document'
        '400':
          description: Bad request - Invalid file type, size, or parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Document holder not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '422':
          description: The verification provider rejected the file. `precheckErrors` names each problem it found. No document is created, so re-upload a corrected file with `POST /documents`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error422'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List documents
      description: |
        Retrieve a list of documents with optional filtering by document holder.
      operationId: listDocuments
      tags:
        - Documents
      security:
        - BasicAuth: []
      parameters:
        - name: documentHolder
          in: query
          description: Filter by document holder ID (Customer or BeneficialOwner)
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /documents/{documentId}:
    get:
      summary: Get a document by ID
      description: Retrieve details and metadata of a specific document by ID.
      operationId: getDocument
      tags:
        - Documents
      security:
        - BasicAuth: []
      parameters:
        - name: documentId
          in: path
          description: Document ID
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Document'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    put:
      summary: Replace a document
      description: |
        Replace an existing document with a new file and/or updated metadata.
        The request must use multipart/form-data.

        Use this when a stored document was rejected during review, which arrives
        as an entry in the verification's `errors` array rather than as an error
        on upload. Replacing marks the previously submitted file inactive, which
        a second `POST /documents` would not do: that leaves the rejected file
        active alongside the new one, and the rejection can carry. Call
        `POST /verifications` afterwards to start a new review, since existing
        errors persist until a new review produces a new verdict.

        A file rejected on upload with `422 DOCUMENT_REJECTED` never creates a
        document, so there is nothing to replace. Retry those with
        `POST /documents`.

        The request follows the same per-type field rules as
        `POST /documents`, except that `documentHolder` isn't sent. Grid
        overwrites every metadata field with the values in this request, so
        resend `documentNumber`, `issuingAuthority`, and `side` even if they
        haven't changed.

        Supported file types: `application/pdf`, `image/jpeg`, and `image/png`.
        Grid matches on the `Content-Type` of the multipart part, not the file
        extension. Any other type, and any file over 10 MB, returns
        `400 INVALID_INPUT`.

        Grid forwards the file to its verification provider, which screens it as
        the request is handled and can reject it with `422 DOCUMENT_REJECTED`.
        To pass that screen, a photo or scan of a document must:

        - show the whole document, with all four corners inside the frame and
          nothing overlapping an edge
        - be in focus and free of glare, so every field and the machine-readable
          zone can be read
        - be in color, not a black-and-white copy
        - be a photo or scan of the physical document, not a screen capture, and
          not retouched in an image editor
        - be unexpired
      operationId: replaceDocument
      tags:
        - Documents
      security:
        - BasicAuth: []
      parameters:
        - name: documentId
          in: path
          description: Document ID
          required: true
          schema:
            type: string
      requestBody:
        $ref: '#/components/requestBodies/DocumentReplaceRequestBody'
      responses:
        '200':
          description: Document replaced successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Document'
        '400':
          description: Bad request - Invalid file type, size, or parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '422':
          description: The verification provider rejected the file. `precheckErrors` names each problem it found. The stored document keeps its previous file and metadata, so retry with a corrected file.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error422'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Delete a document
      description: |
        Delete an uploaded document. This cannot be undone. Documents that have already been submitted for verification may not be deletable.
      operationId: deleteDocument
      tags:
        - Documents
      security:
        - BasicAuth: []
      parameters:
        - name: documentId
          in: path
          description: Document ID
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Document deleted successfully
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - Document cannot be deleted (already submitted for verification)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /verifications:
    post:
      summary: Submit customer for verification
      description: |
        Trigger KYC (individual) or KYB (business) verification for a customer.
        The response indicates whether all required information has been provided.
        If data is missing, the `errors` array describes exactly what needs to be
        supplied before verification can proceed.

        Call this endpoint again after resolving errors to re-submit.

        ### What to collect for KYB

        Before submitting a `BUSINESS` customer, collect the following via
        `POST /customers`, `POST /beneficial-owners`, and `POST /documents`:

        **Business identifying information**
        - Entity full legal name
        - Doing Business As (DBA) name, if applicable
        - Physical address — principal place of business
        - Countries of operation
        - Identification number — U.S. taxpayer identification number, or, for a
          foreign business without one, alternative government-issued documentation
          certifying the existence of the business

        **Ownership and control structure** — collected for **one control person**
        (an individual with significant responsibility to control, manage, or
        direct the legal entity) **and all beneficial owners** (every individual
        who owns 25% or more, directly or indirectly). For each, provide:
        - Full name
        - Date of birth
        - Address
        - Identification number:
          - U.S. persons — SSN or ITIN
          - Non-U.S. persons — one or more of: ITIN, passport (with country of
            issuance), alien identification card, or another government-issued
            photo ID evidencing nationality or residence

        **Required documents**
        - Company formation and existence documents (certificate of incorporation,
          articles of association, etc.)
        - Proof of ownership and control structure (organization and ownership
          chart, shareholder agreements, operating agreements, register of members,
          or certification of controlling person and beneficial owners)
        - Proof of address dated within the last 3 months (utility bill, bank
          statement, lease agreement, or official correspondence)
        - Tax ID or equivalent identifying-number documents
        - For non-U.S. beneficial owners — passport plus one additional
          government-issued ID
      operationId: createVerification
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerificationRequest'
      responses:
        '200':
          description: |
            Verification status returned. Check `verificationStatus` and `errors` to determine next steps.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Verification'
              examples:
                missingInfo:
                  summary: Verification blocked by missing data
                  value:
                    id: Verification:019542f5-b3e7-1d02-0000-000000000001
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    verificationStatus: RESOLVE_ERRORS
                    errors:
                      - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001
                        type: MISSING_FIELD
                        field: customer.address.line1
                        reason: Business address line 1 is required
                      - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001
                        type: MISSING_PROOF_OF_ADDRESS_DOCUMENT
                        acceptedDocumentTypes:
                          - PROOF_OF_ADDRESS
                        reason: Proof of address document is required
                      - resourceId: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000002
                        type: MISSING_FIELD
                        field: personalInfo.birthDate
                        reason: Date of birth is required for beneficial owners
                    createdAt: '2025-10-03T12:00:00Z'
                submitted:
                  summary: Verification submitted successfully
                  value:
                    id: Verification:019542f5-b3e7-1d02-0000-000000000002
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    verificationStatus: IN_PROGRESS
                    errors: []
                    createdAt: '2025-10-03T12:00:00Z'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List verifications
      description: |
        Retrieve a list of verifications with optional filtering by customer ID and status.
      operationId: listVerifications
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      parameters:
        - name: customerId
          in: query
          description: Filter by customer ID
          required: false
          schema:
            type: string
        - name: verificationStatus
          in: query
          description: Filter by verification status
          required: false
          schema:
            $ref: '#/components/schemas/VerificationStatus'
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /verifications/{verificationId}:
    get:
      summary: Get a verification
      description: Retrieve details of a specific verification by ID.
      operationId: getVerification
      tags:
        - KYC/KYB Verifications
      security:
        - BasicAuth: []
      parameters:
        - name: verificationId
          in: path
          description: Verification ID
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Verification'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Verification not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /transfer-in:
    post:
      summary: Create a transfer-in request
      deprecated: true
      description: |
        **Deprecated. Use `POST /quotes` instead.**

        Same-currency transfers are now served by the quote endpoint. Create a quote
        with an external account source and an internal account destination and set
        `immediatelyExecute: true` to move the funds in a single request, exactly as
        this endpoint does. This endpoint continues to work and its request and
        response shapes are unchanged.

        To migrate a request to `POST /quotes`:

        - add `sourceType: ACCOUNT` to `source` and `destinationType: ACCOUNT` to
          `destination`; the account IDs are unchanged
        - rename `amount` to `lockedCurrencyAmount` and add `lockedCurrencySide: SENDING`
        - add `immediatelyExecute: true` to keep the single-request behavior

        The quote response is a `Quote` rather than a `Transaction`; read
        `transactionId` from it to track the resulting transaction.

        Transfer funds from an external account to an internal account for a specific customer.
        This endpoint should only be used for external account sources with pull functionality (e.g. ACH Pull).
        Otherwise, use the paymentInstructions on the internal account to deposit funds.
      operationId: createTransferIn
      tags:
        - Same-Currency Transfers
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: |
            A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request.
          schema:
            type: string
            maxLength: 255
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransferInRequest'
            examples:
              transferIn:
                summary: Transfer from external to internal account
                value:
                  source:
                    accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                  destination:
                    accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                  amount: 12550
      responses:
        '200':
          description: Transfer-in request created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionOneOf'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /transfer-out:
    post:
      summary: Create a transfer-out request
      deprecated: true
      description: |
        **Deprecated. Use `POST /quotes` instead.**

        Same-currency transfers are now served by the quote endpoint. Create a quote
        with an internal account source and an external account destination and set
        `immediatelyExecute: true` to move the funds in a single request, exactly as
        this endpoint does. This endpoint continues to work and its request and
        response shapes are unchanged.

        To migrate a request to `POST /quotes`:

        - add `sourceType: ACCOUNT` to `source` and `destinationType: ACCOUNT` to
          `destination`; the account IDs and `destination.paymentRail` are unchanged
        - rename `amount` to `lockedCurrencyAmount` and add `lockedCurrencySide: SENDING`
        - `remittanceInformation` and `purposeOfPayment` carry over unchanged
        - add `immediatelyExecute: true` to keep the single-request behavior

        The quote response is a `Quote` rather than a `Transaction`; read
        `transactionId` from it to track the resulting transaction.

        Transfer funds from an internal account to an external account for a specific customer.
      operationId: createTransferOut
      tags:
        - Same-Currency Transfers
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: |
            A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request.
          schema:
            type: string
            maxLength: 255
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransferOutRequest'
            examples:
              transferOut:
                summary: Transfer from internal to external account
                value:
                  source:
                    accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                  destination:
                    accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    paymentRail: ACH
                  amount: 12550
                  remittanceInformation: '12345'
      responses:
        '200':
          description: |
            Transfer-out request created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionOneOf'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer or account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. Returned with `BANK_ACCOUNT_VALIDATION_PENDING` when the US bank account on the request is still being validated. Grid verifies a newly added ACH account by sending a micro-entry and waiting out the return window, which takes a few banking days. The account is not rejected, so retry once validation completes. An account that failed validation outright returns `400` with `INVALID_BANK_ACCOUNT`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '429':
          description: Too many requests. Returned with `DAILY_VOLUME_LIMIT_EXCEEDED` when the requested amount would exceed the configured daily volume limit for the withdrawal currency.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /receiver/uma/{receiverUmaAddress}:
    get:
      summary: Look up an UMA address for payment
      description: |
        Lookup a receiving UMA address to determine supported currencies and exchange rates.
        This endpoint helps platforms determine what currencies they can send to a given UMA address.
      operationId: lookupUma
      tags:
        - Cross-Currency Transfers
      security:
        - BasicAuth: []
      parameters:
        - name: receiverUmaAddress
          in: path
          description: UMA address of the intended recipient
          required: true
          schema:
            type: string
        - name: senderUmaAddress
          in: query
          description: UMA address of the sender (optional if customerId is provided)
          required: false
          schema:
            type: string
        - name: customerId
          in: query
          description: System ID of the sender (optional if senderUmaAddress is provided)
          required: false
          schema:
            type: string
        - name: sendingCurrency
          in: query
          description: 'Currency code the sender will send from (e.g., USD). Selects which of the sender''s configured currencies the response is priced against: it is echoed back as the response''s `sendingCurrency`, and the `minSendingAmount`/`maxSendingAmount` bounds are denominated in its smallest unit. Defaults to the sender''s default currency.'
          required: false
          schema:
            type: string
          example: USD
      responses:
        '200':
          description: Successful lookup
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReceiverUmaLookupResponse'
        '400':
          description: Bad request - Missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: UMA address not found. The address is well-formed but no receiver exists at the counterparty VASP (`UMA_NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '412':
          description: Counterparty doesn't support UMA version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error412'
        '424':
          description: Counterparty issue
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error424'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '502':
          description: Counterparty returned an error or an invalid response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error502'
        '504':
          description: Counterparty could not be reached or timed out
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error504'
  /receiver/external-account/{accountId}:
    get:
      summary: Look up an external account for payment
      description: |
        Lookup an external account by ID to determine supported currencies and exchange rates.
        This endpoint helps platforms determine what currencies they can send to a given external account, along with the current estimated exchange rates and minimum and maximum amounts that can be sent.
      operationId: lookupExternalAccount
      tags:
        - Cross-Currency Transfers
      security:
        - BasicAuth: []
      parameters:
        - name: accountId
          in: path
          description: System-generated ID of the external account
          required: true
          schema:
            type: string
            example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
        - name: senderUmaAddress
          in: query
          description: UMA address of the sender (optional if customerId is provided)
          required: false
          schema:
            type: string
        - name: customerId
          in: query
          description: System ID of the sender (optional if senderUmaAddress is provided)
          required: false
          schema:
            type: string
        - name: sendingCurrency
          in: query
          description: 'Currency code the sender will send from (e.g., USD). Selects which of the sender''s configured currencies the response is priced against: it is echoed back as the response''s `sendingCurrency`, and the `minSendingAmount`/`maxSendingAmount` bounds are denominated in its smallest unit. Defaults to the sender''s default currency.'
          required: false
          schema:
            type: string
          example: USD
      responses:
        '200':
          description: Successful lookup
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReceiverExternalAccountLookupResponse'
        '400':
          description: Bad request - Missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '412':
          description: Counterparty doesn't support UMA version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error412'
        '424':
          description: Counterparty issue
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error424'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '502':
          description: Counterparty returned an error or an invalid response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error502'
        '504':
          description: Counterparty could not be reached or timed out
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error504'
  /payment-documents:
    post:
      summary: Upload a payment document
      description: |
        Upload a supporting document for a payout that requires one. [Supporting
        Documents](https://docs.lightspark.com/payouts-and-b2b/payment-flow/send-payment#supporting-documents)
        lists which payouts need documents, the documents each
        `purposeOfPayment` needs, and what the payout partner checks in them.
        Upload each file here,
        then pass the returned `id` values in `documentIds` on `POST /quotes`.
        Grid attaches the files to the payment before it returns the quote.

        The request must use multipart/form-data with the file in the `file`
        field and metadata in the remaining fields. Each request uploads one file.
        To upload several files, send one request per file. You can send them in
        parallel. A quote accepts up to 3 documents.

        Supported formats are PDF, JPEG, and PNG. Grid detects the format from the
        file contents, not the file name. The file must be from 1 to 8,000,000
        bytes. An empty file, a larger file, and any other format return
        `400 INVALID_INPUT`.

        A payment document:

        - can be used until its `expiresAt`, 24 hours after upload
        - can be used on one quote only, and only on a quote for the customer in
          `customerId`, or on the platform's own quote when `customerId` is
          omitted
        - has its file deleted by Grid once it is attached to the payment

        Grid does not check what a document says. The payout partner reviews each
        document after Grid attaches it, and may reject or delay the payout. A
        successful attachment means the payout partner received the file, not
        that it approved it.
      operationId: uploadPaymentDocument
      tags:
        - Cross-Currency Transfers
      security:
        - BasicAuth: []
      requestBody:
        $ref: '#/components/requestBodies/PaymentDocumentUploadRequestBody'
      responses:
        '201':
          description: Payment document uploaded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentDocument'
              example:
                id: PaymentDocument:019542f5-b3e7-1d02-0000-000000000001
                customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                documentType: INVOICE
                fileName: invoice-2025-0142.pdf
                sizeBytes: 482133
                contentType: application/pdf
                status: UPLOADED
                expiresAt: '2025-10-04T12:00:00Z'
                createdAt: '2025-10-03T12:00:00Z'
        '400':
          description: Bad request. Returned with `INVALID_INPUT` when the file is empty, larger than 8,000,000 bytes, or not a PDF, JPEG, or PNG, when `documentType` is not a valid value, or when a required field is missing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Customer not found. Returned with `CUSTOMER_NOT_FOUND` when `customerId` does not match a customer on your platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /payment-documents/{paymentDocumentId}:
    get:
      summary: Get a payment document
      description: Retrieve a payment document by ID. Use it to check whether the document can still be used on a quote, or which quote it is attached to.
      operationId: getPaymentDocument
      tags:
        - Cross-Currency Transfers
      security:
        - BasicAuth: []
      parameters:
        - name: paymentDocumentId
          in: path
          description: Payment document ID
          required: true
          schema:
            type: string
            example: PaymentDocument:019542f5-b3e7-1d02-0000-000000000001
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentDocument'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Payment document not found. Returned with `PAYMENT_DOCUMENT_NOT_FOUND` when the ID does not match a payment document on your platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /quotes/{quoteId}:
    get:
      summary: Get quote by ID
      description: |
        Retrieve a quote by its ID. If the quote has been settled, it will include
        the transaction ID. This allows clients to track the full lifecycle of a payment
        from quote creation to settlement.
      operationId: getQuoteById
      tags:
        - Cross-Currency Transfers
      security:
        - BasicAuth: []
      parameters:
        - name: quoteId
          in: path
          description: ID of the quote to retrieve
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Quote retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Quote not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /quotes:
    post:
      summary: Create a transfer quote
      description: |
        Generate a quote for a transfer between any combination of accounts
        and UMA addresses. This endpoint handles same-currency and cross-currency
        transfers alike, and provides the necessary instructions to execute the transfer.

        **Transfer Types Supported:**
        - **Account to Account**: Transfer between internal/external accounts, with or without currency exchange.
        - **Account to UMA**: Transfer from an internal account to an UMA address.
        - **UMA to Account or UMA to UMA**: This transfer type will only be funded by payment instructions, not from an internal account.

        **Key Features:**
        - **Flexible Amount Locking**: Always specify whether you want to lock the sending amount or receiving amount
        - **Currency Exchange**: Handles all cross-currency transfers with real-time exchange rates
        - **Payment Instructions**: For UMA or customer ID sources, provides banking details needed for execution

        **Same-currency transfers:** Use this endpoint for same-currency transfers too.
        Set `immediatelyExecute: true` to create and execute in a single request. The
        older `/transfer-in` and `/transfer-out` endpoints are deprecated.

        Requires a token with the `TRANSACT` permission; `VIEW` alone is not
        sufficient. A quote is the instrument a later execute draws on, and
        `immediatelyExecute` moves funds within this same request.
      operationId: createQuote
      tags:
        - Cross-Currency Transfers
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: |
            A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request.
            Required when the request includes `documentIds`. A request that fails with `409 PAYMENT_DOCUMENT_ALREADY_USED`, `410 PAYMENT_DOCUMENT_EXPIRED`, or `424 PAYMENT_DOCUMENT_ATTACHMENT_FAILED` created no quote, so a retry with the same key runs the request again.
          schema:
            type: string
            maxLength: 255
            example: <uuid>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteRequest'
            examples:
              sameCurrencyAccountToAccount:
                summary: Same-Currency Account to Account Transfer (immediate execution)
                value:
                  source:
                    sourceType: ACCOUNT
                    accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                  destination:
                    destinationType: ACCOUNT
                    accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    paymentRail: ACH
                  lockedCurrencySide: SENDING
                  lockedCurrencyAmount: 12550
                  immediatelyExecute: true
                  remittanceInformation: INV-12345
                  description: Same-currency payout, no exchange required.
              accountToAccount:
                summary: Account to Account Transfer
                value:
                  source:
                    sourceType: ACCOUNT
                    accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                  destination:
                    destinationType: ACCOUNT
                    accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                  lockedCurrencySide: SENDING
                  lockedCurrencyAmount: 10000
                  description: Transfer between accounts, either internal or external.
              accountToUma:
                summary: Account to UMA Address Transfer
                value:
                  lookupId: LookupRequest:019542f5-b3e7-1d02-0000-000000000009
                  source:
                    sourceType: ACCOUNT
                    accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                  destination:
                    destinationType: UMA_ADDRESS
                    umaAddress: $receiver@uma.domain.com
                    currency: EUR
                  lockedCurrencySide: SENDING
                  lockedCurrencyAmount: 1000
                  description: 'Payment for invoice #1234'
              realTimeFundingToSparkWallet:
                summary: Real-time funding to Spark Wallet as an on-ramp flow. Immediate execution.
                value:
                  source:
                    sourceType: REALTIME_FUNDING
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000009
                    currency: USD
                  destination:
                    destinationType: ACCOUNT
                    accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456
                  lockedCurrencySide: RECEIVING
                  lockedCurrencyAmount: 10000
                  immediatelyExecute: true
                  description: Bitcoin reward payout!
              payoutWithSupportingDocuments:
                summary: Payout with supporting documents
                value:
                  lookupId: Lookup:019542f5-b3e7-1d02-0000-000000000009
                  source:
                    sourceType: ACCOUNT
                    accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                  destination:
                    destinationType: ACCOUNT
                    accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789
                  lockedCurrencySide: RECEIVING
                  lockedCurrencyAmount: 7250000
                  purposeOfPayment: ACCOUNTING_SERVICES
                  documentIds:
                    - PaymentDocument:019542f5-b3e7-1d02-0000-000000000001
                    - PaymentDocument:019542f5-b3e7-1d02-0000-000000000002
                    - PaymentDocument:019542f5-b3e7-1d02-0000-000000000003
                  description: Accounting services, invoice INV-2025-0142
      responses:
        '200':
          description: |
            Transfer quote created successfully. The response includes exchange rates,
            fees, and transfer details. For transfers involving UMA addresses, payment
            instructions are also included for execution through banking systems.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
              example:
                id: Quote:019542f5-b3e7-1d02-0000-000000000006
                status: PENDING
                createdAt: '2025-10-03T12:00:00Z'
                expiresAt: '2025-10-03T12:05:00Z'
                source:
                  sourceType: ACCOUNT
                  accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                destination:
                  destinationType: ACCOUNT
                  accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                sendingCurrency:
                  code: USD
                  name: United States Dollar
                  symbol: $
                  decimals: 2
                receivingCurrency:
                  code: EUR
                  name: Euro
                  symbol: €
                  decimals: 2
                totalSendingAmount: 10000
                totalReceivingAmount: 9200
                exchangeRate: 0.92
                feesIncluded: 10
                platformFeesIncluded: 0
                transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000005
        '202':
          description: |
            Quote created but awaiting Strong Customer Authentication. Returned only for customers whose provider requires SCA (e.g. EU) on a realtime-funding source: the quote has status `PENDING_AUTHORIZATION` and carries an `scaChallenge`, and `paymentInstructions` are **withheld** until the challenge is authorized via `POST /quotes/{quoteId}/authorize`. Once authorized, the quote's `paymentInstructions` are populated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '400':
          description: Bad request. Returned with `DOCUMENTS_REQUIRED` when the destination requires supporting documents for the `purposeOfPayment` and `documentIds` does not supply all of them. `details.missingRequirements` lists the requirement ID of each missing document. Returned with `INVALID_INPUT` for missing or invalid parameters. These include `documentIds` without an `Idempotency-Key` header, `documentIds` sent to a destination that does not require documents, a document whose `documentType` fills no remaining requirement for the `purposeOfPayment`, and a `purposeOfPayment` the destination does not accept.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden. The token lacks the `TRANSACT` permission, or the customer has not accepted the End User Terms.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Not found. Returned with `PAYMENT_DOCUMENT_NOT_FOUND` when an ID in `documentIds` does not match a payment document of the sending customer on your platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '408':
          description: Request Timeout - the request body could not be read in time; retry
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error408'
        '409':
          description: |-
            Conflict. Returned with `BANK_ACCOUNT_VALIDATION_PENDING` when the US bank account on the request is still being validated. Grid verifies a newly added ACH account by sending a micro-entry and waiting out the return window, which takes a few banking days. The account is not rejected, so retry once validation completes. An account that failed validation outright returns `400` with `INVALID_BANK_ACCOUNT`.
            Returned with `PAYMENT_DOCUMENT_ALREADY_USED` when a document in `documentIds` is already attached to a quote. A document can be used on one quote only. Upload the file again and retry with the new ID. Grid created no quote, so you can reuse the same `Idempotency-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '410':
          description: Gone. Returned with `PAYMENT_DOCUMENT_EXPIRED` when a document in `documentIds` passed its `expiresAt` before it was attached. Upload the file again and retry with the new ID. Grid created no quote, so you can reuse the same `Idempotency-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error410'
        '412':
          description: Counterparty doesn't support UMA version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error412'
        '424':
          description: Counterparty issue. Returned with `PAYMENT_DOCUMENT_ATTACHMENT_FAILED` when Grid could not attach a supporting document to the payment. Grid created no quote. Retry the same request with the same `Idempotency-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error424'
        '429':
          description: Too many requests. Returned with `DAILY_VOLUME_LIMIT_EXCEEDED` when `immediatelyExecute` is set and the requested amount would exceed the configured daily volume limit for the trade corridor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
        '502':
          description: Counterparty returned an error or an invalid response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error502'
        '504':
          description: Counterparty could not be reached or timed out
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error504'
  /quotes/{quoteId}/execute:
    post:
      summary: Execute a quote
      description: |
        Execute a quote by its ID. This endpoint initiates the transfer between
        the source and destination accounts.

        This endpoint can only be used for quotes with a `source` which is either an internal account,
        or has direct pull functionality (e.g. ACH pull with an external account).

        When the quote's `source` is an internal account of type `EMBEDDED_WALLET`,
        the request must include a `Grid-Wallet-Signature` header. The header value
        is the full Grid wallet signature built over the `payloadToSign` value from
        the quote's `paymentInstructions[].accountOrWalletInfo` entry with the
        session private key of a verified authentication credential on the source
        Embedded Wallet.

        Requires a token with the `TRANSACT` permission; `VIEW` alone is not
        sufficient to release a transfer. On an `EMBEDDED_WALLET` source this is in
        addition to the `Grid-Wallet-Signature` header: the signature proves the
        wallet holder authorized the payment, while `TRANSACT` is what authorizes
        your integration to release it.

        Once executed, the quote cannot be cancelled and the transfer will be processed.
      operationId: executeQuote
      tags:
        - Cross-Currency Transfers
      security:
        - BasicAuth: []
      parameters:
        - name: quoteId
          in: path
          required: true
          description: The unique identifier of the quote to execute
          schema:
            type: string
          example: Quote:019542f5-b3e7-1d02-0000-000000000001
        - name: Idempotency-Key
          in: header
          required: false
          description: |
            A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request.
          schema:
            type: string
            maxLength: 255
            example: <uuid>
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExecuteQuoteRequest'
      responses:
        '200':
          description: |
            Quote processed. The outcome depends on whether SCA applies to the customer (currently EU customers):

            - **No SCA required:** the transfer is initiated and the quote status advances (`PROCESSING` / `COMPLETED`).

            - **SCA required:** the transfer is **not** initiated yet. The quote is returned with status `PENDING_AUTHORIZATION` and an `scaChallenge`; release the transfer by authorizing the quote you already hold — `POST /quotes/{quoteId}/authorize` (re-calling `execute` returns 409). A pre-funded send is a single challenge, so one authorization releases it. The challenge (and its SMS code / passkey assertion) only comes into existence once this call initiates the transfer, so the proof is always supplied on the follow-up authorize, never inline on this call. If an SMS code lapses, re-send it via `POST /quotes/{quoteId}/authorize/resend`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '400':
          description: Bad request - Invalid quote ID or quote cannot be confirmed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden. The token lacks the `TRANSACT` permission.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Quote not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - Quote already confirmed, expired, or in invalid state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '429':
          description: Too many requests. Returned with `DAILY_VOLUME_LIMIT_EXCEEDED` when executing the quote would exceed the configured daily volume limit for the trade corridor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /quotes/{quoteId}/authorize:
    parameters:
      - name: quoteId
        in: path
        description: The unique identifier of the quote whose SCA challenge is being authorized.
        required: true
        schema:
          type: string
        example: Quote:019542f5-b3e7-1d02-0000-000000000006
    post:
      summary: Authorize a quote's SCA challenge
      description: |
        Satisfy the Strong Customer Authentication challenge carried by a quote in
        `PENDING_AUTHORIZATION` status by submitting an `ScaAuthorization` proof.

        This is used for realtime-funding quotes: the quote is returned with an
        `scaChallenge` and **without** `paymentInstructions`; once authorized, the
        quote advances and its `paymentInstructions` are populated so the customer
        can fund the transfer.

        As with all SCA, a quote may require more than one authorization: after
        authorizing, if the quote is still `PENDING_AUTHORIZATION` it carries the
        next `scaChallenge` — authorize that too, repeating until it advances (see
        `ScaChallenge`).

        This endpoint is only meaningful for customers in a region where SCA is required (e.g. EU). For customers outside SCA-regulated regions, this returns `409`.

        In sandbox, the SMS code is always `123456`.
      operationId: authorizeQuote
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScaAuthorization'
      responses:
        '200':
          description: Challenge authorized; the updated quote is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '400':
          description: Invalid or expired authorization proof
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Quote not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer, or the quote has no pending challenge to authorize. Also returned with `SCA_SESSION_REQUIRED` when the customer's SCA login session is missing or expired — re-authenticate the customer, then authorize again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '423':
          description: Returned with `ACCOUNT_LOCKED` when the customer's login is temporarily locked after too many failed authorization attempts. The customer must wait for the lock to expire before authorizing again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error423'
        '429':
          description: Too many requests. Returned with `RATE_LIMITED` when authorization attempts for this challenge happen too frequently (for example, repeated bad codes brute-forcing the OTP). The challenge may be invalidated after too many failed attempts. Clients should back off and retry after the interval indicated by the `Retry-After` response header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /quotes/{quoteId}/authorize/resend:
    parameters:
      - name: quoteId
        in: path
        description: The unique identifier of the quote whose SCA challenge code should be re-sent.
        required: true
        schema:
          type: string
        example: Quote:019542f5-b3e7-1d02-0000-000000000006
    post:
      summary: Resend a quote's SCA challenge code
      description: |
        Re-send the one-time code for a realtime-funding quote in
        `PENDING_AUTHORIZATION` status whose `scaChallenge.factor` is `SMS_OTP`. The
        existing challenge is reused — no new challenge is issued, and its
        `scaChallenge.expiresAt` is **not** extended; once the challenge is past
        `expiresAt` it can no longer be authorized.

        Only meaningful for customers in a region where SCA is required (e.g. EU);
        a 409 is returned otherwise. `PASSKEY` challenges cannot be re-sent and return 409.

        In sandbox, the code is always `123456`.
      operationId: resendQuoteScaCode
      tags:
        - Strong Customer Authentication
      security:
        - BasicAuth: []
      responses:
        '204':
          description: Code re-sent.
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Quote not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: SCA is not required for this customer, the quote has no pending SMS challenge, or the challenge uses a factor whose code cannot be re-sent (e.g. passkey).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '429':
          description: Too many requests. Returned with `RATE_LIMITED` when codes are re-sent too frequently, to avoid spamming the customer's phone. Clients should back off and retry after the interval indicated by the `Retry-After` response header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /transactions:
    get:
      summary: List transactions
      description: |
        Retrieve a paginated list of transactions with optional filtering.
        The transactions can be filtered by customer ID, platform customer ID, UMA address,
        date range, status, and transaction type.

        Card transactions are included and identified by `type: CARD`. In Sandbox this is how
        you discover a `CardTransaction` id after simulating an authorization — list the
        transactions, take the card transaction's `id`, and pass it as the `cardTransactionId`
        to the clearing and return simulate endpoints.
      operationId: listTransactions
      tags:
        - Transactions
      security:
        - BasicAuth: []
      parameters:
        - name: customerId
          in: query
          description: Filter by system customer ID. To filter to transactions made on behalf of the platform, specify the platform ID as the customer ID.
          required: false
          schema:
            type: string
        - name: platformCustomerId
          in: query
          description: Filter by platform-specific customer ID
          required: false
          schema:
            type: string
        - name: accountIdentifier
          in: query
          description: Filter by account identifier (matches either sender or receiver)
          required: false
          schema:
            type: string
        - name: senderAccountIdentifier
          in: query
          description: Filter by sender account identifier
          required: false
          schema:
            type: string
        - name: receiverAccountIdentifier
          in: query
          description: Filter by receiver account identifier
          required: false
          schema:
            type: string
        - name: status
          in: query
          description: 'Filter by transaction status. Applies to payment transactions only: card transactions carry a card lifecycle status (see `CardTransactionStatus`) and are excluded from status-filtered queries. Combining `status` with `type=CARD` is rejected with a 400.'
          required: false
          schema:
            $ref: '#/components/schemas/TransactionStatus'
        - name: type
          in: query
          description: Filter by transaction type
          required: false
          schema:
            $ref: '#/components/schemas/TransactionType'
        - name: cardId
          in: query
          description: Filter to card transactions made on a single card. Accepts a `Card:` LSID or a bare UUID. Only card transactions match, so this implies `type=CARD` and can be combined with `customerId` to list one card of a multi-card cardholder.
          required: false
          schema:
            type: string
            example: Card:019542f5-b3e7-1d02-0000-000000000010
        - name: reference
          in: query
          description: Filter by reference
          required: false
          schema:
            type: string
        - name: startDate
          in: query
          description: Filter by start date (inclusive) in ISO 8601 format
          required: false
          schema:
            type: string
            format: date-time
        - name: endDate
          in: query
          description: Filter by end date (inclusive) in ISO 8601 format
          required: false
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
        - name: sortOrder
          in: query
          description: Order to sort results in
          required: false
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /transactions/{transactionId}:
    parameters:
      - name: transactionId
        in: path
        description: Unique identifier of the transaction
        required: true
        schema:
          type: string
    get:
      summary: Get transaction by ID
      description: Retrieve detailed information about a specific transaction.
      operationId: getTransactionById
      tags:
        - Transactions
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionOneOf'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /transactions/{transactionId}/confirm:
    post:
      summary: Confirm receipt delivery
      description: |
        Confirm that the platform delivered the transaction receipt to its customer. This confirmation is only necessary when the platform is contractually required to send a receipt. If `receiptDeliveryConfirmedAt` is omitted, the confirmation time is set to the current server time. Calling this endpoint again for the same transaction updates the stored confirmation time.
      operationId: confirmReceiptDelivery
      tags:
        - Transactions
      security:
        - BasicAuth: []
      parameters:
        - name: transactionId
          in: path
          description: Unique identifier of the transaction to confirm receipt delivery for
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfirmReceiptDeliveryRequest'
      responses:
        '200':
          description: Receipt delivery confirmation recorded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionOneOf'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /transactions/{transactionId}/approve:
    post:
      summary: Approve a pending incoming payment
      description: |
        Approve a pending incoming payment that was previously acknowledged with a 202 response.
        This endpoint allows platforms to asynchronously approve payments after async processing.
      operationId: approvePendingPayment
      tags:
        - Transactions
      security:
        - BasicAuth: []
      parameters:
        - name: transactionId
          in: path
          description: Unique identifier of the transaction to approve
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApprovePaymentRequest'
      responses:
        '200':
          description: Payment approved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IncomingTransaction'
        '400':
          description: Bad request - Invalid parameters or payment cannot be approved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - Payment is not in a pending state or has already been processed or timed out.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /transactions/{transactionId}/reject:
    post:
      summary: Reject a pending incoming payment
      description: |
        Reject a pending incoming payment that was previously acknowledged with a 202 response.
        This endpoint allows platforms to asynchronously reject payments after additional processing.
      operationId: rejectPendingPayment
      tags:
        - Transactions
      security:
        - BasicAuth: []
      parameters:
        - name: transactionId
          in: path
          description: Unique identifier of the transaction to reject
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RejectPaymentRequest'
      responses:
        '200':
          description: Payment rejected successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IncomingTransaction'
        '400':
          description: Bad request - Invalid parameters or payment cannot be rejected
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - Payment is not in a pending state or has already been processed or timed out.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /transactions/{transactionId}/cancel:
    post:
      summary: Cancel a bank transfer
      description: |
        Request cancellation of a pending bank transfer — an ACH transfer (push or pull) or a wire — before it has settled, for example a payment or collection initiated outside of the receiving bank's processing window.
        Whether a transfer can still be cancelled is determined by the banking partner that is settling it: the request is forwarded to the partner's own cancellation facility, and a transfer that the partner has already processed (or that is otherwise past its cancellation window) cannot be cancelled. Cancellation applies to bank-rail transfers; requests for transaction types that cannot be cancelled are rejected.
      operationId: cancelTransaction
      tags:
        - Transactions
      security:
        - BasicAuth: []
      parameters:
        - name: transactionId
          in: path
          description: Unique identifier of the transaction to cancel
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelTransactionRequest'
      responses:
        '200':
          description: Cancellation requested successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionOneOf'
        '400':
          description: Bad request - Invalid parameters or the transaction cannot be cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - The transaction has already settled or is otherwise past the point where it can be cancelled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /crypto/estimate-withdrawal-fee:
    post:
      summary: Estimate crypto withdrawal fee
      description: |
        Estimate the network and application fees for a cryptocurrency withdrawal from a crypto internal account to an external blockchain address. Use this to show fee information to customers before they initiate a withdrawal.
      operationId: estimateCryptoWithdrawalFee
      tags:
        - Cross-Currency Transfers
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeRequest'
            examples:
              estimateUSDCWithdrawal:
                summary: Estimate fee for USDC withdrawal on Solana
                value:
                  internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                  currency: USDC
                  cryptoNetwork: SOLANA
                  amount: 1000000
                  destinationAddress: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
              estimateArbitrumWithdrawal:
                summary: Estimate fee for USDC withdrawal on Arbitrum
                value:
                  internalAccountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                  currency: USDC
                  cryptoNetwork: ARBITRUM
                  amount: 1000000
                  destinationAddress: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
      responses:
        '200':
          description: Fee estimation returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EstimateCryptoWithdrawalFeeResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Internal account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /webhooks/test:
    post:
      summary: Send a test webhook
      description: Deliver a signed test event to the platform's configured webhook endpoint and return the endpoint's response. Available in sandbox and production.
      operationId: sendTestWebhook
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Webhook delivered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestWebhookResponse'
        '400':
          description: Bad request - Webhook delivery failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/bulk/csv:
    post:
      summary: Upload customers via CSV file
      description: |
        Upload a CSV file containing customer information for bulk creation. The CSV file should follow
        a specific format with required and optional columns based on customer type.

        ### CSV Format
        The CSV file should have the following columns:

        Required columns for all customers:
        - umaAddress: The customer's UMA address (e.g., $john.doe@uma.domain.com)
        - platformCustomerId: Your platform's unique identifier for the customer
        - customerType: Either "INDIVIDUAL" or "BUSINESS"

        Required columns for individual customers:
        - fullName: Individual's full name
        - birthDate: Date of birth in YYYY-MM-DD format
        - addressLine1: Street address line 1
        - city: City
        - state: State/Province/Region
        - postalCode: Postal/ZIP code
        - country: Country code (ISO 3166-1 alpha-2)

        Required columns for business customers:
        - businessLegalName: Legal name of the business
        - addressLine1: Street address line 1
        - city: City
        - state: State/Province/Region
        - postalCode: Postal/ZIP code
        - country: Country code (ISO 3166-1 alpha-2)

        Optional columns for all customers:
        - addressLine2: Street address line 2
        - platformAccountId: Your platform's identifier for the bank account
        - description: Optional description for the customer

        Optional columns for individual customers:
        - email: Customer's email address

        Optional columns for business customers:
        - businessRegistrationNumber: Business registration number
        - businessTaxId: Tax identification number

        ### Example CSV
        ```csv
        umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,platformAccountId,businessLegalName
        john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US
        acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US
        ```

        The upload process is asynchronous and will return a job ID that can be used to track progress.
        You can monitor the job status using the `/customers/bulk/jobs/{jobId}` endpoint.
      operationId: uploadCustomersCsv
      tags:
        - Customers
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: CSV file containing customer information
      responses:
        '202':
          description: CSV upload accepted for processing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkCustomerImportJobAccepted'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /customers/bulk/jobs/{jobId}:
    get:
      summary: Get bulk import job status
      description: |
        Retrieve the current status and results of a bulk customer import job. This endpoint can be used
        to track the progress of both CSV uploads.

        The response includes:
        - Overall job status
        - Progress statistics
        - Detailed error information for failed entries
        - Completion timestamp when finished
      operationId: getBulkCustomerImportJob
      tags:
        - Customers
      security:
        - BasicAuth: []
      parameters:
        - name: jobId
          in: path
          description: ID of the bulk import job to retrieve
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Job status retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkCustomerImportJob'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /invitations:
    post:
      summary: Create an UMA invitation
      description: |
        Create an UMA invitation from a given platform customer.
      operationId: createInvitation
      tags:
        - Invitations
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UmaInvitationCreateRequest'
      responses:
        '201':
          description: Invitation created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UmaInvitation'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /invitations/{invitationCode}:
    get:
      summary: Get an UMA invitation by code
      description: |
        Retrieve details about an UMA invitation by its invitation code.
      operationId: getInvitation
      tags:
        - Invitations
      security:
        - BasicAuth: []
      parameters:
        - name: invitationCode
          in: path
          description: The code of the invitation to get
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Invitation retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UmaInvitation'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Invitation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /invitations/{invitationCode}/claim:
    post:
      summary: Claim an UMA invitation
      description: |
        Claim an UMA invitation by associating it with an invitee UMA address.

        When an invitation is successfully claimed:
        1. The invitation status changes from PENDING to CLAIMED
        2. The invitee UMA address is associated with the invitation
        3. An INVITATION_CLAIMED webhook is triggered to notify the platform that created the invitation

        This endpoint allows customers to accept invitations sent to them by other UMA customers.
      operationId: claimInvitation
      tags:
        - Invitations
      security:
        - BasicAuth: []
      parameters:
        - name: invitationCode
          in: path
          description: The code of the invitation to claim
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UmaInvitationClaimRequest'
      responses:
        '200':
          description: Invitation claimed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UmaInvitation'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Invitation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /invitations/{invitationCode}/cancel:
    post:
      summary: Cancel an UMA invitation
      description: |
        Cancel a pending UMA invitation. Only the inviter or platform can cancel an invitation.

        When an invitation is cancelled:
        1. The invitation status changes from PENDING to CANCELLED
        2. The invitation can no longer be claimed
        3. The invitation URL will show as cancelled when accessed

        Only pending invitations can be cancelled. Attempting to cancel an invitation
        that is already claimed, expired, or cancelled will result in an error.
      operationId: cancelInvitation
      tags:
        - Invitations
      security:
        - BasicAuth: []
      parameters:
        - name: invitationCode
          in: path
          description: The code of the invitation to cancel
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Invitation cancelled successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UmaInvitation'
        '400':
          description: Bad request - Invitation cannot be cancelled (already claimed, expired, or cancelled)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - Only the platform which created the invitation can cancel it
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Invitation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/send:
    post:
      summary: Simulate sending funds
      description: |
        Simulate sending funds to the bank account as instructed in the quote.
        This endpoint is only for the sandbox environment and will fail for production platforms/keys.
      operationId: sandboxSend
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxSendRequest'
      responses:
        '200':
          description: Funds received successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionOneOf'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Quote not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/uma/receive:
    post:
      summary: Simulate payment send to test receiving an UMA payment
      description: |
        Simulate sending payment from an sandbox uma address to a platform customer to test payment receive.
        This endpoint is only for the sandbox environment and will fail for production platforms/keys.
      operationId: sandboxReceive
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxUmaReceiveRequest'
      responses:
        '200':
          description: Payment triggered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IncomingTransaction'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Sender or receiver not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/internal-accounts/{accountId}/fund:
    post:
      summary: Simulate funding an internal account
      description: |
        Simulate receiving funds into an internal account in the sandbox environment. This is useful for testing scenarios where you need to add funds to a customer's or platform's internal account without going through a real bank transfer or following payment instructions.
        This endpoint is only for the sandbox environment and will fail for production platforms/keys.
        ### Funding a rule-based account
        Funding a `RULE_BASED` account triggers its sweep, exactly as a settled deposit does in production: the balance is swept to the rule's destination and the account is left at zero. A balance outside the corridor band moves to the account holder's own account in the same currency instead, and the transaction fails with `failureReason: SWEEP_AMOUNT_OUT_OF_RANGE` — fund a rule-based account with a very small amount to rehearse that path. Either way the rule-based account is left at zero; it never holds a balance.
        The sweep's transaction reports a placeholder ACH depositor as its `REALTIME_FUNDING` source and the funded account as its `ruleBasedAccountId`, matching what a production deposit reports.
        To simulate the information a depositor attaches to a payment, pass `remittanceInformation`. It appears on the swept transaction's `source` (see `remittanceInformation` in the request body for which field).
        `description` cannot be set when funding a rule-based account: the swept transaction's description comes from the account's sweep rule, exactly as it does for a production deposit. Sending it returns `400 INVALID_INPUT`.
        ### Funding other account types
        `description` sets the memo on the incoming transaction for the credit. With `remittanceInformation` set, the credit is reported as a `REALTIME_FUNDING` ACH deposit from a placeholder sandbox depositor, matching what a production bank deposit reports.
        The request is validated before any funds are credited, so a request rejected with `400` leaves the balance unchanged.
        Sandbox simulates the API contract and the routing decision, not the banking rails. The quote, the corridor bounds, the destination checks and the webhook are all real; the settlement legs behind them are not, so timings and failure modes that originate at a partner bank cannot be reproduced here.
      operationId: sandboxFundInternalAccount
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: accountId
          in: path
          required: true
          description: The ID of the internal account to fund
          schema:
            type: string
          example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxFundRequest'
            examples:
              fundUSDAccount:
                summary: Fund USD internal account with $1,000
                value:
                  amount: 100000
              fundBTCAccount:
                summary: Fund BTC internal account with 0.01 BTC
                value:
                  amount: 1000000
              fundUSDAccountWithDescription:
                summary: Fund USD internal account with a memo
                value:
                  amount: 100000
                  description: Test deposit for invoice reconciliation
              fundUSDAccountWithRemittance:
                summary: Fund USD internal account with depositor remittance information
                value:
                  amount: 100000
                  remittanceInformation: INV-2026-0042
              fundRuleBasedAccountWithRemittance:
                summary: Fund a rule-based account with depositor remittance information
                value:
                  amount: 100000
                  remittanceInformation: INV-2026-0042
      responses:
        '200':
          description: Internal account funded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalAccount'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Internal account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /tokens:
    post:
      summary: Create a new API token
      description: Create a new API token to access the Grid APIs.
      operationId: createToken
      tags:
        - API Tokens
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiTokenCreateRequest'
      responses:
        '201':
          description: API token created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiToken'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List tokens
      description: |
        Retrieve a list of API tokens with optional filtering parameters. Returns all tokens that match
        the specified filters. If no filters are provided, returns all tokens (paginated).
      operationId: listTokens
      tags:
        - API Tokens
      security:
        - BasicAuth: []
      parameters:
        - name: name
          in: query
          description: Filter by name of the token
          required: false
          schema:
            type: string
        - name: createdAfter
          in: query
          description: Filter customers created after this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: createdBefore
          in: query
          description: Filter customers created before this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: updatedAfter
          in: query
          description: Filter customers updated after this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: updatedBefore
          in: query
          description: Filter customers updated before this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /tokens/{tokenId}:
    parameters:
      - name: tokenId
        in: path
        description: System-generated unique token identifier
        required: true
        schema:
          type: string
    get:
      summary: Get API token by ID
      description: Retrieve an API token by their system-generated ID
      operationId: getTokenById
      tags:
        - API Tokens
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiToken'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Token not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Delete API token by ID
      description: Delete an API token by their system-generated ID
      operationId: deleteTokenById
      tags:
        - API Tokens
      security:
        - BasicAuth: []
      responses:
        '204':
          description: API token deleted successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Token not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /internal-accounts/{id}:
    parameters:
      - name: id
        in: path
        description: The id of the internal account to update.
        required: true
        schema:
          type: string
        example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
    patch:
      summary: Update internal account
      description: |
        Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account.

        Updating wallet privacy is a two-step signed-retry flow:

        1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`.

        2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account.
      operationId: updateInternalAccount
      tags:
        - Internal Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: Request:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InternalAccountUpdateRequest'
            examples:
              updateWalletPrivacy:
                summary: Update wallet privacy request (both steps)
                value:
                  privateEnabled: true
      responses:
        '200':
          description: Signed retry accepted. Returns the updated internal account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalAccount'
              examples:
                enabled:
                  summary: Wallet privacy enabled
                  value:
                    id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    type: EMBEDDED_WALLET
                    status: ACTIVE
                    balance:
                      amount: 12550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    totalBalance:
                      amount: 12550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    fundingPaymentInstructions: []
                    privateEnabled: true
                    createdAt: '2026-04-08T15:30:00Z'
                    updatedAt: '2026-04-08T15:35:02Z'
        '202':
          description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignedRequestChallenge'
              examples:
                challenge:
                  summary: Internal account update challenge
                  value:
                    payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"encoding":"PAYLOAD_ENCODING_HEXADECIMAL","hashFunction":"HASH_FUNCTION_NO_OP","payload":"9f3b...","signWith":"sp1q..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"}'
                    requestId: Request:019542f5-b3e7-1d02-0000-000000000010
                    expiresAt: '2026-04-08T15:35:00Z'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Internal account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /internal-accounts/{id}/export:
    post:
      summary: Export internal account wallet credentials
      description: |
        Export the wallet credentials of an Embedded Wallet internal account. The returned wallet credentials are HPKE-encrypted to the `clientPublicKey` supplied in the request body.

        Export is a two-step signed-retry flow (same pattern as add-additional credential, revoke credential, and revoke session):

        1. Call `POST /internal-accounts/{id}/export` with the request body `{ "clientPublicKey": "..." }` and no signature headers. Grid binds the `clientPublicKey` into the `payloadToSign` it returns, so the subsequent stamp in `Grid-Wallet-Signature` commits to the target encryption key. The response is `202` with `payloadToSign`, `requestId`, and `expiresAt`.

        2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the **same** `clientPublicKey` submitted in step 1 — Grid rejects the retry with `401` if it disagrees with what was bound into `payloadToSign`. The signed retry returns `200` with `encryptedWalletCredentials`, which the client decrypts with the matching private key.

        The export may not settle within that request: an approval- or consensus-gated wallet-provider activity answers `200` with `status: "PROCESSING"` instead. The credentials are never stored server-side, so collecting them is the client's job — re-send the byte-identical signed retry (same headers, same body) until it returns `encryptedWalletCredentials`. The `Request-Id` challenge stays usable until an attempt actually delivers them, so a `PROCESSING` response never burns it; a delivered export does, and a later re-send is then rejected with `401`. Subscribe to `wallet_operation.completed` to learn when re-sending will succeed rather than polling blind.

        The `clientPublicKey` is ephemeral: generate a fresh P-256 keypair for this export and discard the private key after decrypting. Do not reuse the keypair from any prior verify call — that private key was already discarded after decrypting the session signing key it was issued against.
      operationId: exportInternalAccount
      tags:
        - Internal Accounts
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          description: The id of the internal account to export.
          required: true
          schema:
            type: string
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InternalAccountExportRequest'
            examples:
              export:
                summary: Export request (both steps)
                value:
                  clientPublicKey: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2
      responses:
        '200':
          description: 'Signed retry accepted. Returns the encrypted wallet credentials (`InternalAccountExportResponse`); or, when the underlying wallet-provider activity is still in flight, a `WalletOperationProcessing` body with `status: "PROCESSING"` — the credentials are never stored server-side, so the client re-sends the byte-identical signed retry to collect them once the activity settles. The challenge stays usable until an attempt actually returns credentials.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalAccountExportResult'
        '202':
          description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `clientPublicKey`) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignedRequestChallenge'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending export challenge for this internal account, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Internal account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /internal-accounts/{id}/balance-changes:
    parameters:
      - name: id
        in: path
        description: The id of the internal account to list balance changes for.
        required: true
        schema:
          type: string
        example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
    get:
      summary: List balance changes for a period
      description: |
        Every change to this account's balance in a window, in the order the money moved, with the opening and closing balances for that window in the same response.

        `GET /transactions` returns one row per transaction. This returns one row per change to the balance, and a transaction that moves the balance more than once produces more than one change: an ACH deposit and its later return are two, and a card purchase that clears in two parts is two. Each change's `transactionId` names its transaction, so fetch it from `GET /transactions/{transactionId}` for its type, counterparty, merchant and rail.

        **The identity to assert:** `openingBalance + Σ(data[].amount) == closingBalance`, summed over every page. The opening and closing balances describe the window rather than the page, so they are the same on every page. Page until `hasMore` is false, then assert the identity.

        `startDate` and `endDate` are instants in any timezone, UTC included, and the window is half-open: a change at exactly `endDate` belongs to the next window. Consecutive windows therefore tile with no gap and no overlap.

        A window whose card settlement has not closed is refused with `409 NOT_YET_AVAILABLE`, because its figures could still change. Retry once it has settled.

        A fee is already inside a change's `amount`. For the fee itself, read `fees` on the change's transaction, counting each transaction once.
      operationId: listInternalAccountBalanceChanges
      tags:
        - Periodic Statements
      security:
        - BasicAuth: []
      parameters:
        - name: startDate
          in: query
          required: true
          description: Start of the window, inclusive, in ISO 8601 format. Must carry a timezone, either `Z` for UTC or an offset.
          schema:
            type: string
            format: date-time
          example: '2026-08-01T00:00:00-05:00'
        - name: endDate
          in: query
          required: true
          description: End of the window, exclusive, in ISO 8601 format. Must carry a timezone, either `Z` for UTC or an offset, and must not be in the future.
          schema:
            type: string
            format: date-time
          example: '2026-09-01T00:00:00-05:00'
        - name: limit
          in: query
          required: false
          description: Maximum number of changes to return per page
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 100
        - name: cursor
          in: query
          required: false
          description: 'Cursor for pagination: the `nextCursor` from the previous page, unchanged. Send it with the same account, `startDate` and `endDate` as that page. A cursor does not expire.'
          schema:
            type: string
      responses:
        '200':
          description: The balance changes in the window, with the balances that bound it
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BalanceChangeListResponse'
        '400':
          description: Bad request. Returned when `startDate` or `endDate` is missing, has no timezone, or is in the future, when `startDate` is not before `endDate`, when `cursor` was not issued by this endpoint for the same account and window, or when the account has no statement of its own.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Internal account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: The window is not final yet. Card settlement for one of its boundaries has not closed, so its balances and changes could still change. Retry once it has settled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /internal-accounts/{id}/confirm-statement:
    parameters:
      - name: id
        in: path
        description: The id of the internal account whose statement was delivered.
        required: true
        schema:
          type: string
        example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
    post:
      summary: Send a receipt for a periodic statement
      description: |
        Record your monthly receipt that you pulled and issued this account's periodic statement for a period. Grid stores it as the delivery record for that account and period.

        Send it once a month for each account, after you have issued the statement built from `GET /internal-accounts/{id}/balance-changes`. Send it within five days of the month's end for every customer account: Grid checks then that every account's statement was issued.

        `statementMonth` names the statement month as `YYYY-MM`, a calendar month in US Central time. It must be a month that has already begun.

        Sending a receipt is idempotent. The stored time is the first receipt's, so calling this again does not move it.
      operationId: confirmStatementDelivery
      tags:
        - Periodic Statements
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfirmStatementDeliveryRequest'
      responses:
        '200':
          description: Receipt recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatementDelivery'
        '400':
          description: Bad request. Returned when `statementMonth` is not a `YYYY-MM` month or names a month that has not begun, when `statementDeliveredAt` is missing, in the future or before the period has ended, or when the account has no statement of its own.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Internal account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: The period is not final yet. Card settlement for the month has not closed, so its statement could still change and cannot have been issued. Send the receipt once `GET /internal-accounts/{id}/balance-changes` serves the month.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/credentials:
    post:
      summary: Create an authentication credential
      description: |
        Register an authentication credential for an Embedded Wallet customer.

        Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered.

        Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential's details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (the session signing key the client holds for it) to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `201` with the created `AuthMethod`. For OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`.
      operationId: createAuthCredential
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf'
            examples:
              emailOtp:
                summary: Add an email OTP credential
                value:
                  type: EMAIL_OTP
                  accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
              smsOtp:
                summary: Add an SMS OTP credential
                value:
                  type: SMS_OTP
                  accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
              emailOtpReplacement:
                summary: Register a replacement email address
                value:
                  type: EMAIL_OTP
                  accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                  email: jane.new@example.com
              smsOtpReplacement:
                summary: Register a replacement phone number
                value:
                  type: SMS_OTP
                  accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                  phoneNumber: '+14155559876'
              oauth:
                summary: Add an OAuth credential
                value:
                  type: OAUTH
                  accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                  oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q
              passkey:
                summary: Add a passkey credential
                value:
                  type: PASSKEY
                  accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                  nickname: iPhone Face-ID
                  challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx
                  attestation:
                    credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY
                    clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0
                    attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow
                    transports:
                      - internal
                      - hybrid
      responses:
        '200':
          description: 'An additional-credential setup leg''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the byte-identical stamped retry (same `Request-Id`) until the credential is added; the backend also reconciles the activity to terminal on its own.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletOperationProcessing'
        '201':
          description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthMethodResponse'
              examples:
                emailOtp:
                  summary: Email OTP credential created
                  value:
                    id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: EMAIL_OTP
                    nickname: example@lightspark.com
                    otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}'''
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:30:01Z'
                smsOtp:
                  summary: SMS OTP credential created
                  value:
                    id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: SMS_OTP
                    nickname: '+14155550123'
                    otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}'''
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:30:01Z'
                oauth:
                  summary: OAuth credential created
                  value:
                    id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: OAUTH
                    nickname: example@lightspark.com
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:30:01Z'
                passkey:
                  summary: Passkey credential created
                  value:
                    id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: PASSKEY
                    nickname: iPhone Face-ID
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:30:01Z'
        '202':
          description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSignedRequestChallenge'
              examples:
                emailOtp:
                  summary: Additional email OTP credential challenge
                  value:
                    type: EMAIL_OTP
                    payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}'
                    requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
                smsOtp:
                  summary: Additional SMS OTP credential challenge
                  value:
                    type: SMS_OTP
                    payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}'
                    requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
                oauth:
                  summary: Additional OAuth credential challenge
                  value:
                    type: OAUTH
                    payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}'
                    requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
                passkey:
                  summary: Additional passkey credential challenge
                  value:
                    type: PASSKEY
                    payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}'
                    requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
        '400':
          description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Internal account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. Returned with `OPERATION_IN_PROGRESS`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '429':
          description: Too many requests. Returned with `RATE_LIMITED`.
          headers:
            Retry-After:
              description: Number of seconds to wait before retrying the request.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List authentication credentials
      description: |-
        Retrieve all authentication credentials registered on an Embedded Wallet internal account.

        The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`.
      operationId: listAuthCredentials
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: accountId
          in: query
          description: Internal account id whose authentication credentials to list.
          required: true
          schema:
            type: string
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
      responses:
        '200':
          description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthCredentialListResponse'
              examples:
                multipleCredentials:
                  summary: Internal account with multiple authentication credentials
                  value:
                    data:
                      - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                        accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                        type: EMAIL_OTP
                        nickname: example@lightspark.com
                        createdAt: '2026-04-08T15:30:01Z'
                        updatedAt: '2026-04-08T15:30:01Z'
                      - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004
                        accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                        type: OAUTH
                        nickname: example@lightspark.com
                        createdAt: '2026-04-08T15:35:00Z'
                        updatedAt: '2026-04-08T15:35:00Z'
                      - id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003
                        accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                        type: PASSKEY
                        credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
                        nickname: iPhone Face-ID
                        createdAt: '2026-04-09T10:15:00Z'
                        updatedAt: '2026-04-09T10:15:00Z'
                empty:
                  summary: No credentials registered on the account
                  value:
                    data: []
        '400':
          description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:<uuid>` identifier.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/credentials/{id}:
    delete:
      summary: Revoke an authentication credential
      description: |
        Revoke an authentication credential on an Embedded Wallet internal account.

        Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account:

        1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`.

        2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`.

        The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it.
      operationId: revokeAuthCredential
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          description: The id of the authentication credential to revoke (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`).
          required: true
          schema:
            type: string
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
      responses:
        '200':
          description: 'The revocation''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the same signed `DELETE` until it settles; the backend also reconciles the revocation to terminal on its own.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletOperationProcessing'
        '202':
          description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSignedRequestChallenge'
        '204':
          description: Authentication credential revoked successfully.
        '400':
          description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Authentication credential not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. Returned with `OPERATION_IN_PROGRESS`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/credentials/{id}/verify:
    post:
      summary: Verify an authentication credential
      description: |
        Complete the verification step for a previously created authentication credential and issue a session.

        For `EMAIL_OTP` and `SMS_OTP` credentials, submit the `encryptedOtpBundle` produced by HPKE-encrypting `{otp_code, public_key}` under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued. The server is a pass-through and never sees the plaintext OTP code. On success the response is `202` with a `payloadToSign` containing a login signing message bound to the client's TEK public key. Build an API-key stamp over the exact UTF-8 bytes of that string with the matching TEK private key, without parsing, re-serializing, or trimming it. Then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`. The signed retry returns `200` with the issued `AuthSession`. The TEK public key becomes the session API key on successful completion.
        In sandbox mode, the OTP flow runs real HPKE end-to-end against a sandbox enclave keypair — clients build a real `encryptedOtpBundle` against the sandbox `otpEncryptionTargetBundle` and stamp the returned `payloadToSign` with their TEK keypair. The only sandbox shortcut is the magic OTP code (`"000000"`) the user "receives" instead of a real email or SMS delivery.

        For `OAUTH` credentials, supply a fresh OIDC token (`iat` must be less than 60 seconds before the request) along with the client-generated public key; this is also the reauthentication path after a prior session expired. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified. In sandbox, the token's `nonce` must equal `sha256(clientPublicKey)`. For `PASSKEY` credentials, the client completes a WebAuthn assertion (`navigator.credentials.get()`) against the Grid-issued `challenge` returned from `POST /auth/credentials/{id}/challenge`, and submits the resulting `assertion` with the `Request-Id` header. The `clientPublicKey` for `PASSKEY` credentials is supplied on the challenge call, where it is bound into the pending session-creation request.

        On success for `OAUTH` and `PASSKEY`, and on the signed retry for OTP credentials, the response contains an `AuthSession`. Sending a compressed `clientPublicKey` selects the recommended client-held-key model: the client already holds the session signing key — the private key it generated before authentication — so no key material is returned and the deprecated `encryptedSessionSigningKey` is omitted. Sending an uncompressed `clientPublicKey` selects the deprecated legacy flow, where the session signing key is HPKE-sealed to that key and returned as `encryptedSessionSigningKey` for the client to decrypt. The `expiresAt` timestamp marks when the session expires.
      operationId: verifyAuthCredential
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          description: The id of the authentication credential to verify (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`).
          required: true
          schema:
            type: string
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the exact UTF-8 bytes of the prior `payloadToSign` string, without parsing, re-serializing, or trimming it, with the TEK (Target Encryption Key) keypair generated for this login. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification. Not used by `OAUTH` or `PASSKEY` verification, which complete in a single call.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in a prior `202` response from this endpoint, echoed back exactly here so the server can correlate the signed retry with the issued challenge. Required on the signed retry that completes an `EMAIL_OTP` or `SMS_OTP` verification; must be paired with `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId` issued from `POST /auth/credentials/{id}/challenge` is echoed here instead so the server can correlate the assertion with the pending challenge.
          schema:
            type: string
          example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf'
            examples:
              emailOtp:
                summary: Verify an email OTP credential (first leg)
                value:
                  type: EMAIL_OTP
                  encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}'
              smsOtp:
                summary: Verify an SMS OTP credential (first leg)
                value:
                  type: SMS_OTP
                  encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}'
              emailOtpSignedRetry:
                summary: Signed retry completing an email OTP verification
                description: Same request body as the first leg, plus the `Grid-Wallet-Signature` and `Request-Id` headers carrying the stamp over the exact `payloadToSign` string and the `requestId` from the prior `202` response.
                value:
                  type: EMAIL_OTP
                  encryptedOtpBundle: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}'
              oauth:
                summary: Verify an OAuth credential
                value:
                  type: OAUTH
                  oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q
                  clientPublicKey: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31
              passkey:
                summary: Verify a passkey credential
                value:
                  type: PASSKEY
                  assertion:
                    credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
                    clientDataJson: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0
                    authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA
                    signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900
      responses:
        '200':
          description: 'Authentication credential verified and session issued (`AuthSession`); or, when the underlying wallet-provider activity is still in flight, a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical request until it gets the session, and the backend also reconciles the activity to terminal on its own.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthCredentialVerifyResponse'
        '202':
          description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over the exact UTF-8 bytes of the returned `payloadToSign` string with the TEK keypair the client generated for this login. Preserve the string unchanged, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSignedRequestChallenge'
              examples:
                emailOtp:
                  summary: Email OTP verification challenge (sign and retry)
                  value:
                    type: EMAIL_OTP
                    payloadToSign: '{"login":{"publicKey":"02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31"},"tokenId":"7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21","type":"USAGE_TYPE_LOGIN"}'
                    requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
        '204':
          description: The replacement credential is verified.
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP` or `SMS_OTP`), for an OIDC token whose signature, issuer, identity, nonce, or `iat` freshness check failed (`OAUTH`), or for a WebAuthn assertion whose signature, challenge, or credential match failed (`PASSKEY`). Also returned for `PASSKEY` when `Request-Id` is missing, does not match an unexpired pending challenge for this credential, or was already consumed. For OTP signed retries, returned when `Grid-Wallet-Signature` is missing, malformed, signed by a public key that does not match the one bound into the `verificationToken`, or when `Request-Id` does not match an unexpired pending verification challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Authentication credential not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '429':
          description: Too many requests. Returned with `RATE_LIMITED` when verification attempts for this credential happen too frequently (for example, repeated bad OTPs or rapid-fire reauthentication retries). Clients should back off and retry after the interval indicated by the `Retry-After` response header.
          headers:
            Retry-After:
              description: Number of seconds to wait before retrying the request.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/credentials/{id}/challenge:
    post:
      summary: Re-issue an authentication credential challenge
      description: |
        Re-issue the challenge for an existing authentication credential.

        For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the client to HPKE-encrypt the OTP attempt against. After the user receives the new OTP, build the `encryptedOtpBundle` under the new target bundle and call `POST /auth/credentials/{id}/verify` to begin the secure OTP login flow.

        `OAUTH` credentials do not have a challenge step. To authenticate or reauthenticate an OAuth credential, call `POST /auth/credentials/{id}/verify` with a fresh OIDC token and a `clientPublicKey`.

        For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from — send a compressed key for the recommended client-held-key model, where the client retains the matching private key as the resulting session signing key, or an uncompressed key for the deprecated legacy flow. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: <requestId>` to receive a session.
      operationId: challengeAuthCredential
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          description: The id of the authentication credential to re-challenge (the `id` field of the `AuthMethod` returned from `POST /auth/credentials`).
          required: true
          schema:
            type: string
      requestBody:
        description: Request body. Required when re-challenging a `PASSKEY` credential (must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient — the OTP is delivered out-of-band. OAuth credentials do not use this endpoint.
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthCredentialChallengeRequest'
            examples:
              passkey:
                summary: Re-challenge a passkey credential
                value:
                  clientPublicKey: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31
              emailOtp:
                summary: Re-challenge an email-OTP credential (empty body)
                value: {}
              smsOtp:
                summary: Re-challenge an SMS-OTP credential (empty body)
                value: {}
      responses:
        '200':
          description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. When the OTP send's underlying wallet-provider activity is still in flight, the body is instead a `WalletOperationProcessing` carrying a `PROCESSING` status — re-request the challenge until the send settles; the backend also reconciles it to terminal on its own.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthCredentialChallengeResponse'
              examples:
                emailOtp:
                  summary: Email OTP challenge re-issued
                  value:
                    id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: EMAIL_OTP
                    nickname: example@lightspark.com
                    otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}'''
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:35:00Z'
                passkey:
                  summary: Passkey reauthentication challenge issued
                  value:
                    id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: PASSKEY
                    credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
                    nickname: iPhone Face-ID
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:35:00Z'
                    challenge: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729
                    requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Authentication credential not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '429':
          description: Too many requests. Returned with `RATE_LIMITED` when challenge re-issues are requested more frequently than the credential challenge rate limit allows. Clients should back off and retry after the interval indicated by the `Retry-After` response header.
          headers:
            Retry-After:
              description: Number of seconds to wait before retrying the request.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/sessions:
    get:
      summary: List active sessions
      description: |-
        Retrieve all active authentication sessions on an Embedded Wallet internal account. A session is created each time a credential is verified via `POST /auth/credentials/{id}/verify`, and remains active until its `expiresAt` passes or it is revoked via `DELETE /auth/sessions/{id}`.

        The response is not paginated: an internal account is expected to have a small, bounded number of concurrent sessions (one per signed-in device, typically 1–4), so all results are returned inline.
      operationId: listAuthSessions
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: accountId
          in: query
          description: Internal account id whose sessions to list.
          required: true
          schema:
            type: string
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
      responses:
        '200':
          description: Active authentication sessions on the internal account. Returns an empty `data` array when the internal account has no active sessions or when `accountId` does not match any internal account visible to the caller.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionListResponse'
        '400':
          description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:<uuid>` identifier.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/sessions/{id}:
    delete:
      summary: Revoke an authentication session
      description: |
        Revoke an authentication session on an Embedded Wallet internal account. Revocation is a two-step signed-retry flow:

        1. Call `DELETE /auth/sessions/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`.

        2. Use the session API keypair of a verified session on the same internal account (this can be the session being revoked, for self-logout) to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The signed retry returns `204`.

        Sessions also expire on their own. `404` is returned whenever the `id` does not match an active session — whether the session was never issued, was already revoked by a prior call, or has expired past its `expiresAt`. The response code reflects the resource state, not an error in the client's flow: re-revoking an already-revoked or expired session is safe and idempotent at the user intent level.
      operationId: revokeAuthSession
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          description: The id of the session to revoke.
          required: true
          schema:
            type: string
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified session on the same internal account. Required on the signed retry; ignored on the initial call.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
      responses:
        '200':
          description: 'The revocation''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the same signed `DELETE` until it settles; the backend also reconciles the revocation to terminal on its own.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletOperationProcessing'
        '202':
          description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSignedRequestChallenge'
        '204':
          description: Session revoked successfully.
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending revocation challenge for this session, or when the `Request-Id` does not match an unexpired pending challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/sessions/{id}/refresh:
    post:
      summary: Refresh an authentication session
      description: |
        Refresh an active Embedded Wallet auth session and create a new session signing key. Session refresh is a two-step signed-retry flow:

        1. Call `POST /auth/sessions/{id}/refresh` with the request body `{ "clientPublicKey": "02..." }` and no signature headers. Send a freshly generated client public key and retain its private key. Grid binds the supplied `clientPublicKey` into the session-refresh payload, persists it as a pending request, and returns `202` with `payloadToSign`, `requestId`, and `expiresAt`.

        2. Sign `payloadToSign` with the current session signing key, then retry the same request with the full API-key stamp as `Grid-Wallet-Signature`, the `requestId` echoed back as `Request-Id`, and the same `clientPublicKey` in the request body. On success, Grid returns a new `AuthSession`. Sending a compressed `clientPublicKey` selects the recommended client-held-key model, where the client retains the new session signing key and no key material is returned; sending an uncompressed key selects the deprecated legacy flow, where the new key is sealed to it and returned as `encryptedSessionSigningKey`.

        The original session must still be active on both steps so it can authorize the refresh. If the session has already expired, use the credential reauthentication flow instead.
      operationId: refreshAuthSession
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          description: The id of the active session to refresh.
          required: true
          schema:
            type: string
          example: Session:019542f5-b3e7-1d02-0000-000000000003
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the prior `payloadToSign` with the current session API keypair. Required on the signed retry; ignored on the initial call.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in the prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: Request:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthSessionRefreshRequest'
            examples:
              refresh:
                summary: Refresh an active session
                value:
                  clientPublicKey: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31
      responses:
        '200':
          description: 'The session refresh''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the byte-identical signed retry until it gets the `201`; the backend also reconciles the activity to terminal on its own.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletOperationProcessing'
        '201':
          description: New authentication session created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSession'
              examples:
                session:
                  summary: Refreshed authentication session
                  value:
                    id: Session:019542f5-b3e7-1d02-0000-000000000011
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: EMAIL_OTP
                    nickname: example@lightspark.com
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:35:00Z'
                    expiresAt: '2026-04-08T15:50:00Z'
        '202':
          description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the current session API keypair, then echo `requestId` on the signed retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignedRequestChallenge'
              examples:
                challenge:
                  summary: Session refresh challenge
                  value:
                    payloadToSign: '{"organizationId":"org_abc123","parameters":{"targetPublicKey":"02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31"},"timestampMs":"1746736509954","type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"}'
                    requestId: Request:019542f5-b3e7-1d02-0000-000000000010
                    expiresAt: '2026-04-08T15:35:00Z'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned when the `BasicAuth` credentials are missing or invalid, when the target session is no longer active and cannot be used for refresh, when the signed retry omits `Grid-Wallet-Signature`, when the provided signature is malformed or does not match the pending refresh challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `clientPublicKey` does not match the one bound into `payloadToSign` on the initial call.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/delegated-keys:
    post:
      summary: Create a delegated signing key
      description: |
        Delegate Spark token-transaction signing authority for a card funding source backed by an Embedded Wallet internal account to a Grid-custodied P-256 API key. Grid uses the requested card and internal account to identify the wallet funding source, generates the keypair server-side, creates an isolated signer identity holding the public key, then policies granting that identity signing and self-revocation authority. The private key is custodied by Grid and never returned. Both activities must be authorized by the wallet owner, so creation is a three-leg signed-retry flow:

        1. Call `POST /auth/delegated-keys` with no signature headers. Grid generates the delegated keypair and the response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`.

        2. Use the session API keypair of a verified credential on the requested Embedded Wallet internal account to build an API-key stamp over `payloadToSign`, then retry the same request with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The response is a second `202` with a new `payloadToSign`, `requestId`, and `expiresAt`.

        3. Stamp the new `payloadToSign` with the same session keypair and retry once more with the new `Request-Id`. The signed retry returns `201` with the created `DelegatedKey` in `ACTIVE` status. A provisioned `PENDING_AUTH` card using this funding source becomes `ACTIVE` automatically. Cards still provisioning remain `PROCESSING`; frozen and closed cards retain their status.

        The same request body must be sent on all three legs. A flow abandoned after the second leg leaves the key in `PENDING` status: the signer identity exists but holds no policies, so it cannot sign or revoke itself. Abandoned `PENDING` keys do not block creating another delegated key. After activation, Grid uses the custodied key to authorize signing for the card's Embedded Wallet funding account in place of a session keypair; the platform never handles the key material.

        Each card funding source may have at most one `ACTIVE` delegated key for its Embedded Wallet funding account; revoke the existing active key before creating a new one. A delegated key authorizes raw-payload signing for the wallet and cannot be scoped to amounts or recipients by the public API. Revoke it with `DELETE /auth/delegated-keys/{id}` when no longer needed.
      operationId: createDelegatedKey
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified credential on the same internal account. Required on the signed retries; ignored on the initial call.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: The `requestId` returned in the prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retries; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DelegatedKeyCreateRequest'
            examples:
              create:
                summary: Delegate signing to a Grid-custodied key
                value:
                  cardId: Card:019542f5-b3e7-1d02-0000-000000000010
                  internalAccountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                  nickname: Card payments key
      responses:
        '201':
          description: Delegated key created and policy granted. The key is `ACTIVE` and Grid may use it to stamp card-payment quote executions for this card funding source's Embedded Wallet funding account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DelegatedKey'
              examples:
                delegatedKey:
                  summary: Active delegated key
                  value:
                    id: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021
                    cardId: Card:019542f5-b3e7-1d02-0000-000000000010
                    fundingSourceId: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    publicKey: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90
                    nickname: Card payments key
                    status: ACTIVE
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:30:42Z'
        '202':
          description: Challenge issued for the next leg. Stamp `payloadToSign` and retry the same request with `Grid-Wallet-Signature` and `Request-Id`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DelegatedKeySignedRequestChallenge'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing on a retry, malformed, or does not match the pending challenge, or when the `Request-Id` does not match an unexpired pending challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Card, card funding source, or Embedded Wallet funding account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: An `ACTIVE` delegated key already exists for this card funding source. Revoke it with `DELETE /auth/delegated-keys/{id}` before creating a new one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List delegated signing keys
      description: List delegated signing keys for an Embedded Wallet internal account, a card funding source, or both, including `PENDING` keys (user created but policy leg never completed) and `REVOKED` keys. At least one of `accountId` or `fundingSourceId` must be supplied.
      operationId: listDelegatedKeys
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: accountId
          in: query
          required: false
          description: The id of the internal account whose delegated keys to list.
          schema:
            type: string
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        - name: fundingSourceId
          in: query
          required: false
          description: The id of the card funding source whose delegated keys to list.
          schema:
            type: string
          example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011
      responses:
        '200':
          description: Delegated keys matching the supplied filters. Returns an empty `data` array when no matching delegated keys are visible to the caller.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DelegatedKeyListResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /auth/delegated-keys/{id}:
    get:
      summary: Get a delegated signing key
      description: Retrieve a delegated signing key by its system-generated id.
      operationId: getDelegatedKeyById
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          description: The id of the delegated key to retrieve (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys` or `GET /auth/delegated-keys`).
          required: true
          schema:
            type: string
          example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DelegatedKey'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Delegated key not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Revoke a delegated signing key
      description: |
        Revoke an `ACTIVE` delegated signing key. An active card using this funding source becomes `PENDING_AUTH` with `statusReason: "DELEGATION_REQUIRED"`. Frozen and closed cards retain their status. Grid uses the custodied delegated key to authorize deleting its own signer identity. Deleting the identity also removes its API key, after which the delegated key can no longer sign. The response is `204` when revocation completes.

        The underlying signing policies are left in place. Their consensus references the now-deleted signer identity, so they can never authorize anything, and deleting them is unnecessary for correctness or security.
      operationId: revokeDelegatedKey
      tags:
        - Embedded Wallet Auth
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          description: The id of the delegated key to revoke (the `id` field of the `DelegatedKey` returned from `POST /auth/delegated-keys`).
          required: true
          schema:
            type: string
          example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021
      responses:
        '204':
          description: Delegated key revoked. The key can no longer authorize signing.
        '400':
          description: Bad request. Returned when the delegated key has already been revoked or is not `ACTIVE`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Delegated key not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents:
    post:
      summary: Create an agent
      description: |
        Create a new agent with a specified policy. Returns the created agent and a device code that must be redeemed by the agent software to complete installation.
      operationId: createAgent
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentCreateRequest'
      responses:
        '201':
          description: Agent created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentCreateResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List agents
      description: Retrieve a paginated list of agents for the authenticated platform.
      operationId: listAgents
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      parameters:
        - name: customerId
          in: query
          description: Filter by customer ID
          required: false
          schema:
            type: string
        - name: isPaused
          in: query
          description: Filter by paused status
          required: false
          schema:
            type: boolean
        - name: isConnected
          in: query
          description: Filter by connection status (whether the device code has been redeemed)
          required: false
          schema:
            type: boolean
        - name: createdAfter
          in: query
          description: Filter agents created after this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: createdBefore
          in: query
          description: Filter agents created before this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: updatedAfter
          in: query
          description: Filter agents updated after this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: updatedBefore
          in: query
          description: Filter agents updated before this timestamp (inclusive)
          required: false
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/approvals:
    get:
      summary: List agent transaction approval requests
      description: |
        Retrieve a paginated list of agent actions that require platform approval. Filter by `agentId` or `customerId` to scope results to a specific agent or customer. Approve or reject individual actions via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`.
      operationId: listAgentApprovals
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      parameters:
        - name: agentId
          in: query
          description: Filter by agent ID
          required: false
          schema:
            type: string
        - name: customerId
          in: query
          description: Filter by customer ID
          required: false
          schema:
            type: string
        - name: startDate
          in: query
          description: Filter by start date (inclusive) in ISO 8601 format
          required: false
          schema:
            type: string
            format: date-time
        - name: endDate
          in: query
          description: Filter by end date (inclusive) in ISO 8601 format
          required: false
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
        - name: sortOrder
          in: query
          description: Order to sort results in
          required: false
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentActionListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me:
    get:
      summary: Get current agent
      description: |
        Retrieve the authenticated agent's own profile, policy, and current usage. This endpoint is called by the agent software itself using its own credentials (obtained via device code redemption) rather than platform credentials.
      operationId: getAgentMe
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/{agentId}:
    parameters:
      - name: agentId
        in: path
        description: System-generated unique agent identifier
        required: true
        schema:
          type: string
    get:
      summary: Get agent by ID
      description: Retrieve an agent by its system-generated ID.
      operationId: getAgentById
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    patch:
      summary: Update agent
      description: Update an agent's name or paused state.
      operationId: updateAgent
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentUpdateRequest'
      responses:
        '200':
          description: Agent updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Delete agent
      description: Permanently delete an agent. Connected agent software will lose access immediately.
      operationId: deleteAgent
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      responses:
        '204':
          description: Agent deleted successfully
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/{agentId}/policy:
    parameters:
      - name: agentId
        in: path
        description: System-generated unique agent identifier
        required: true
        schema:
          type: string
    patch:
      summary: Update agent policy
      description: |
        Partially update an agent's policy. Only provided fields will be updated; omitted fields retain their current values. Policy changes take effect immediately.
      operationId: updateAgentPolicy
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentPolicyUpdateRequest'
      responses:
        '200':
          description: Agent policy updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/{agentId}/device-codes:
    parameters:
      - name: agentId
        in: path
        description: System-generated unique agent identifier
        required: true
        schema:
          type: string
    post:
      summary: Regenerate a device code
      description: |
        Generate a new device code for an existing agent. Use this when the original device code has expired before being redeemed, or when the agent software needs to be reinstalled. Any previously issued unredeemed device codes for this agent are invalidated.
      operationId: regenerateAgentDeviceCode
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      responses:
        '201':
          description: New device code generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDeviceCode'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - Agent already has an active connection and cannot regenerate a device code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/{agentId}/actions/{actionId}/approve:
    parameters:
      - name: agentId
        in: path
        description: System-generated unique agent identifier
        required: true
        schema:
          type: string
      - name: actionId
        in: path
        description: Unique identifier of the agent action to approve
        required: true
        schema:
          type: string
    post:
      summary: Approve an agent action
      description: |
        Approve a pending agent action, allowing Grid to proceed with execution. The action must have status `PENDING_APPROVAL`. Once approved, Grid executes the underlying operation (quote execution or transfer) and the action transitions to `APPROVED`.
        For `EXECUTE_QUOTE` actions, note that the underlying quote may have expired between submission and approval — in that case the action will transition to `FAILED` instead.
        This endpoint is called by the platform's backend using platform credentials, not by the agent itself.
      operationId: approveAgentAction
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Action approved successfully. Returns the updated AgentAction.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentAction'
        '400':
          description: Bad request - Action cannot be approved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Agent or action not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - Action is not pending approval or has already been processed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/{agentId}/actions/{actionId}/reject:
    parameters:
      - name: agentId
        in: path
        description: System-generated unique agent identifier
        required: true
        schema:
          type: string
      - name: actionId
        in: path
        description: Unique identifier of the agent action to reject
        required: true
        schema:
          type: string
    post:
      summary: Reject an agent action
      description: |
        Reject a pending agent action, preventing execution. The action must have status `PENDING_APPROVAL`. Once rejected, the action transitions to `REJECTED` and the underlying operation is not executed.
        This endpoint is called by the platform's backend using platform credentials, not by the agent itself.
      operationId: rejectAgentAction
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentActionRejectRequest'
      responses:
        '200':
          description: Action rejected successfully. Returns the updated AgentAction.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentAction'
        '400':
          description: Bad request - Action cannot be rejected
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Agent or action not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - Action is not pending approval or has already been processed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/device-codes/{code}/status:
    parameters:
      - name: code
        in: path
        description: The device code to check
        required: true
        schema:
          type: string
    get:
      summary: Get device code status
      description: |
        Check whether a device code has been redeemed. Use this to poll for agent installation completion after creating an agent.
      operationId: getAgentDeviceCodeStatus
      tags:
        - Agent Management
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDeviceCodeStatusResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Device code not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/device-codes/{code}/redeem:
    parameters:
      - name: code
        in: path
        description: The device code to redeem
        required: true
        schema:
          type: string
    post:
      summary: Redeem device code
      description: |
        Redeem a device code to obtain agent credentials. This endpoint is called by the agent software during installation. On success, returns a Bearer access token that the agent uses for all subsequent API calls. The token is returned only once and must be stored securely.
        This endpoint does not require platform authentication — the device code itself serves as proof of authorization.
      operationId: redeemAgentDeviceCode
      tags:
        - Agent Management
      security: []
      responses:
        '200':
          description: Device code redeemed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentDeviceCodeRedeemResponse'
        '400':
          description: Bad request (e.g., code already redeemed or expired)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '404':
          description: Device code not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/transactions:
    get:
      summary: List agent transactions
      description: |
        Retrieve a paginated list of transactions for the authenticated agent's customer. Results are automatically scoped to the agent's associated customer — no customer filter is needed or accepted.
      operationId: agentListTransactions
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      parameters:
        - name: accountIdentifier
          in: query
          description: Filter by account identifier (matches either sender or receiver)
          required: false
          schema:
            type: string
        - name: senderAccountIdentifier
          in: query
          description: Filter by sender account identifier
          required: false
          schema:
            type: string
        - name: receiverAccountIdentifier
          in: query
          description: Filter by receiver account identifier
          required: false
          schema:
            type: string
        - name: status
          in: query
          description: Filter by transaction status
          required: false
          schema:
            $ref: '#/components/schemas/TransactionStatus'
        - name: type
          in: query
          description: Filter by transaction type
          required: false
          schema:
            $ref: '#/components/schemas/TransactionType'
        - name: reference
          in: query
          description: Filter by reference
          required: false
          schema:
            type: string
        - name: startDate
          in: query
          description: Filter by start date (inclusive) in ISO 8601 format
          required: false
          schema:
            type: string
            format: date-time
        - name: endDate
          in: query
          description: Filter by end date (inclusive) in ISO 8601 format
          required: false
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
        - name: sortOrder
          in: query
          description: Order to sort results in
          required: false
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/transactions/{transactionId}:
    parameters:
      - name: transactionId
        in: path
        description: Unique identifier of the transaction
        required: true
        schema:
          type: string
    get:
      summary: Get agent transaction by ID
      description: |
        Retrieve a specific transaction belonging to the authenticated agent's customer. Returns 404 if the transaction exists but belongs to a different customer.
      operationId: agentGetTransaction
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionOneOf'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/quotes:
    post:
      summary: Create a transfer quote
      description: |
        Generate a quote for a cross-currency transfer on behalf of the authenticated agent's customer. Accounts referenced in the request must belong to the agent's customer. Requires the CREATE_QUOTES permission in the agent's policy.
        If the agent's defaultExecutionMode is APPROVAL_REQUIRED, or the quote amount exceeds the agent's approvalThresholds, the resulting transaction will require explicit approval before funds move.
      operationId: agentCreateQuote
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: |
            A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request.
          schema:
            type: string
            maxLength: 255
            example: <uuid>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteRequest'
      responses:
        '201':
          description: Transfer quote created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '400':
          description: Bad request - Missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - Agent policy does not permit this operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '412':
          description: Counterparty doesn't support UMA version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error412'
        '424':
          description: Counterparty issue
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error424'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '502':
          description: Counterparty returned an error or an invalid response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error502'
        '504':
          description: Counterparty could not be reached or timed out
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error504'
  /agents/me/quotes/{quoteId}:
    parameters:
      - name: quoteId
        in: path
        description: ID of the quote to retrieve
        required: true
        schema:
          type: string
    get:
      summary: Get agent quote by ID
      description: |
        Retrieve a quote created by the authenticated agent. Returns 404 if the quote exists but was not created by this agent.
      operationId: agentGetQuote
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      responses:
        '200':
          description: Quote retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Quote'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Quote not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/quotes/{quoteId}/execute:
    parameters:
      - name: quoteId
        in: path
        required: true
        description: The unique identifier of the quote to execute
        schema:
          type: string
        example: Quote:019542f5-b3e7-1d02-0000-000000000001
    post:
      summary: Execute a quote
      description: |
        Execute a quote created by the authenticated agent. Requires the EXECUTE_QUOTES permission in the agent's policy.
        If the agent's policy requires approval for this amount (based on execution mode or approval thresholds), the transaction will be created in a pending state and must be approved by the platform via `POST /agents/{agentId}/actions/{actionId}/approve`.
        Once executed, the quote cannot be cancelled.
      operationId: agentExecuteQuote
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: |
            A unique identifier for the request. If the same key is sent multiple times, the server will return the same response as the first request.
          schema:
            type: string
            maxLength: 255
            example: <uuid>
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: Full Grid wallet signature over the `payloadToSign` returned in the quote's `paymentInstructions[].accountOrWalletInfo` entry, produced with the session private key of a verified authentication credential on the source Embedded Wallet. Required when the quote's source is an internal account of type `EMBEDDED_WALLET`; ignored for other source types.
          schema:
            type: string
          example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
      responses:
        '200':
          description: 'Action submitted successfully. If the agent''s policy requires approval, the returned `AgentAction` will have status `PENDING_APPROVAL` and no `transaction` yet. If the policy permits automatic execution, status will be `APPROVED` and `transaction` will be populated. Note: if approval is required, the underlying quote may expire before the platform approves — in that case the action will transition to `FAILED`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentAction'
        '400':
          description: Bad request - Quote cannot be executed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Also returned when the quote's source is an internal account of type `EMBEDDED_WALLET` and the provided `Grid-Wallet-Signature` header is missing, malformed, or does not match the quote's `payloadToSign`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - Agent policy does not permit this operation or spending limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Quote not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict - Quote already executed, expired, or in invalid state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/actions:
    get:
      summary: List agent's own actions
      description: |
        Retrieve a paginated list of actions submitted by the authenticated agent. Use this to poll for approval decisions after submitting an action that requires approval.
      operationId: agentListActions
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      parameters:
        - name: status
          in: query
          description: Filter by action status
          required: false
          schema:
            $ref: '#/components/schemas/AgentActionStatus'
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentActionListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/actions/{actionId}:
    parameters:
      - name: actionId
        in: path
        description: Unique identifier of the agent action
        required: true
        schema:
          type: string
    get:
      summary: Get an agent action
      description: |
        Retrieve a specific action submitted by the authenticated agent. Poll this endpoint after submitting an action that requires approval to check whether it has been approved, rejected, or has failed.
      operationId: agentGetAction
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentAction'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Action not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/internal-accounts:
    get:
      summary: List agent's internal accounts
      description: |
        Retrieve the internal accounts belonging to the customer this agent operates on behalf of. Use this to discover available source accounts for transfers and quotes, and to verify which accounts are accessible under the agent's `accountRestrictions` policy.
      operationId: agentListInternalAccounts
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      parameters:
        - name: currency
          in: query
          description: Filter by currency code
          required: false
          schema:
            type: string
        - name: type
          in: query
          description: Filter by internal account type. Use `EMBEDDED_WALLET` to find the self-custodial wallet provisioned for the customer, `INTERNAL_FIAT` / `INTERNAL_CRYPTO` for platform-managed holding accounts, or `RULE_BASED` for the additional account numbers issued with a sweep rule.
          required: false
          schema:
            $ref: '#/components/schemas/InternalAccountType'
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalAccountListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/external-accounts:
    get:
      summary: List agent external accounts
      description: |
        Retrieve a paginated list of external accounts belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy.
      operationId: agentListExternalAccounts
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      parameters:
        - name: currency
          in: query
          description: Filter by currency code
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccountListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    post:
      summary: Add an external account
      description: |
        Register a new external bank account or wallet for the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy. The `customerId` field is optional and will be inferred from the agent's associated customer if omitted.
      operationId: agentCreateExternalAccount
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExternalAccountCreateRequest'
            examples:
              usBankAccount:
                summary: Create external US bank account
                value:
                  currency: USD
                  accountInfo:
                    accountType: USD_ACCOUNT
                    accountNumber: '12345678901'
                    routingNumber: '123456789'
                    bankAccountType: CHECKING
                    bankName: Chase Bank
                    beneficiary:
                      beneficiaryType: INDIVIDUAL
                      fullName: John Doe
                      birthDate: '1990-01-15'
                      nationality: US
                      address:
                        line1: 123 Main Street
                        city: San Francisco
                        state: CA
                        postalCode: '94105'
                        country: US
              sparkWallet:
                summary: Create external Spark wallet
                value:
                  currency: BTC
                  accountInfo:
                    accountType: SPARK_WALLET
                    address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu
      responses:
        '201':
          description: External account created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccount'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - External account already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /agents/me/external-accounts/{externalAccountId}:
    parameters:
      - name: externalAccountId
        in: path
        description: System-generated unique external account identifier
        required: true
        schema:
          type: string
    get:
      summary: Get agent external account by ID
      description: |
        Retrieve an external account belonging to the authenticated agent's customer. Returns 404 if the account exists but belongs to a different customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy.
      operationId: agentGetExternalAccount
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExternalAccount'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Delete agent external account
      description: |
        Delete an external account belonging to the authenticated agent's customer. Requires the MANAGE_EXTERNAL_ACCOUNTS permission in the agent's policy.
      operationId: agentDeleteExternalAccount
      tags:
        - Agent Operations
      security:
        - AgentAuth: []
      responses:
        '204':
          description: External account deleted successfully
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: External account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /cards:
    post:
      summary: Issue a card
      description: |
        Issue a new card for a cardholder. Every card is bound to one internal account, `fundingSource`, at create time. The cardholder must have KYC status `APPROVED` before a card can be issued; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`.

        Card issuance is fee-bearing and cannot be reversed, so an `Idempotency-Key` header is required. Retries must carry the same key.

        Optional `maxSpendPerTransaction`, `maxSpendPerDay`, and `maxTransactionsPerDay` values set the card-specific caps on one transaction, on spend during one UTC calendar day, and on the number of transactions during one UTC calendar day. Check the funding-source internal account's `cardCapabilities.supportsSpendLimitsAtIssuance` before supplying either spend limit, and `cardCapabilities.supportsTransactionCountLimit` before supplying the transaction count limit. If the platform config sets the corresponding `cardConfigs` value, Grid enforces the lower of the card and platform caps. Amounts use the smallest unit of the card's currency.

        If the funding source is an Embedded Wallet internal account, the cardholder must authorize Grid to sign Spark token transactions for that card funding source by completing the delegated-key creation flow with `POST /auth/delegated-keys`. Until an active delegated key exists for that funding source, Authorization Decisioning cannot use it to fund card transactions.

        A platform may be limited to a maximum number of live cards. Once that limit is reached, further issuance is rejected with `CARD_LIMIT_REACHED` until a card is closed or Lightspark raises the limit. Cards in `CLOSED` status do not count toward the limit.

        New cards start in `status: "PROCESSING"` while the card issuer provisions the card. The `card.status_change` webhook fires on each status transition, including the transition to `ACTIVE` (or to `CLOSED` with `statusReason: "ISSUER_REJECTED"` if provisioning fails). A provisioned card funded by a USDB Embedded Wallet remains `PENDING_AUTH` with `statusReason: "DELEGATION_REQUIRED"` until all signed retries of `POST /auth/delegated-keys` are complete. Replacing USD funding with USDB without an active key, or revoking the required key, also moves an active card to `PENDING_AUTH`. Completing delegation or switching back to USD restores `ACTIVE`; frozen and closed cards keep their status.
      operationId: createCard
      tags:
        - Cards
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          description: |
            A unique identifier for the request, up to 255 characters. A retry carrying the same key returns the card created by the first request; reusing a key for a materially different card request is rejected with `409`.
          required: true
          schema:
            type: string
            maxLength: 255
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardCreateRequest'
            examples:
              virtualCard:
                summary: Issue a virtual card with one funding source
                value:
                  customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                  platformCardId: card-emp-001
                  form: VIRTUAL
                  fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                  maxSpendPerTransaction: 5000
                  maxSpendPerDay: 25000
                  maxTransactionsPerDay: 20
      responses:
        '201':
          description: Card created successfully. Newly-created cards start in `PROCESSING` while the issuer provisions them. Cards funded by an Embedded Wallet internal account also require an active delegated key for that funding source before Authorization Decisioning can use it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Card'
        '400':
          description: Bad request. Returned with `CARDHOLDER_KYC_NOT_APPROVED` when the cardholder's KYC status is not `APPROVED`, and with `INVALID_INPUT` when the `Idempotency-Key` header is missing or exceeds 255 characters, when a required `threeDSecurePassword` is missing, empty, or whitespace-only, when the supplied funding source does not belong to the cardholder or is not denominated in a card-eligible currency, and for general invalid parameters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict. Returned with `CARD_LIMIT_REACHED` when the platform has reached the maximum number of live cards it may hold, and with `CONFLICT` when the `Idempotency-Key` was already used for a different card request. Closing a card frees its slot; contact Lightspark to raise the limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Card issuance is not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
    get:
      summary: List cards
      description: |
        Retrieve a paginated list of cards. Cards can be filtered by cardholder, bound funding-source internal account, status, and platform-specific card identifier. If no filters are provided, returns all cards visible to the caller.
      operationId: listCards
      tags:
        - Cards
      security:
        - BasicAuth: []
      parameters:
        - name: customerId
          in: query
          description: Filter by customer id.
          required: false
          schema:
            type: string
        - name: accountId
          in: query
          description: Filter by internal account id. Returns cards whose `fundingSource` is the given internal account id.
          required: false
          schema:
            type: string
        - name: platformCardId
          in: query
          description: Filter by platform-specific card identifier.
          required: false
          schema:
            type: string
        - name: status
          in: query
          description: Filter by card status.
          required: false
          schema:
            $ref: '#/components/schemas/CardStatus'
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
        - name: sortOrder
          in: query
          description: Order to sort results in
          required: false
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CardListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
  /cards/{id}:
    parameters:
      - name: id
        in: path
        description: System-generated unique card identifier
        required: true
        schema:
          type: string
    get:
      summary: Get a card
      description: Retrieve a card by its system-generated id. To display the card's full PAN, CVV, and expiry to the cardholder, request a reveal with `POST /cards/{id}/reveal` — the card resource itself never carries the reveal URL.
      operationId: getCardById
      tags:
        - Cards
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Card'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Card not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
    patch:
      summary: Update a card
      description: |
        Update a card's `status`, bound `fundingSource`, and / or `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`, or set a new `threeDSecurePassword`. At least one field must be supplied.

        - `status` transitions include `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. `CLOSED` is terminal and irreversible. Activating a card whose USDB funding source lacks a usable delegated key returns `409 INVALID_STATE_TRANSITION`. `PENDING_AUTH` is managed by Grid and cannot be requested. Completing delegation does not unfreeze or reopen a card.
        - `fundingSource`, when supplied, replaces the card's bound internal account. It must belong to the customer and use a currency supported by the card program, including USDB for USD cards. An active card becomes `PENDING_AUTH` when switched to USDB without an active delegated key. Switching back to USD restores readiness. `fundingSource` cannot be supplied alongside `status: CLOSED`. On card programs where the card issuer makes authorization decisions, `fundingSource` cannot be combined with any `status` change, so send the changes as separate requests. On card programs where Grid makes the authorization decision, the combination remains valid for `status` changes other than `CLOSED`.
        - `maxSpendPerTransaction` sets the largest amount the card can authorize on a single transaction. An authorization for exactly the limit is allowed, and a later clearing can still settle above it — a restaurant tip, for example — so this caps the authorization, not the final settled amount. Send a positive integer in the smallest unit of the card's currency (cents for USD) to set the limit, or null to remove it. If your platform config sets `cardConfigs.maxSpendPerTransaction`, the lower of the two applies. You can only send this when the card's `cardCapabilities.supportsSpendLimits` is true, and not together with `status: CLOSED`.
        - `maxSpendPerDay`, when supplied, replaces the card-specific cap on cumulative new spend during one UTC calendar day. Supply a positive integer in the smallest unit of the card's currency to set it or null to clear it. If the platform config sets `cardConfigs.maxSpendPerDay`, Grid enforces the lower of the card and platform values. Refunds, reversals, and authorization expiries do not restore capacity during the day. The card's `cardCapabilities.supportsSpendLimits` must be true. `maxSpendPerDay` cannot be supplied alongside `status: CLOSED`.
        - `maxTransactionsPerDay`, when supplied, replaces the card-specific cap on the number of transactions the card may authorize during one UTC calendar day. Supply a positive integer to set it or null to clear it. If the platform config sets `cardConfigs.maxTransactionsPerDay`, Grid enforces the lower of the card and platform values. Refunds, reversals, and authorization expiries do not restore capacity during the day. `maxTransactionsPerDay` requires the card's `cardCapabilities.supportsTransactionCountLimit` to be true and cannot be supplied alongside `status: CLOSED`.

        This endpoint is authenticated by the platform credential alone and returns `200` directly. It deliberately does not use Grid's 202 → signed-retry pattern: that pattern signs with the session key of a credential on the owning internal account, so it models actions taken *by* the end user on their own credentials or funds. Freezing or closing a card is routinely an action taken *about* a user and without them present - fraud response, offboarding, an ops-driven freeze - and requiring the cardholder's signature would make exactly those cases impossible. Operations that expose sensitive card data (`POST /cards/{id}/reveal`, 3DS password retrieval) are SCA-railed instead, because there the cardholder is the party being served.

        Effects:
        - `status: FROZEN`: Authorization Decisioning declines new auths with `cardDeclinedReason: CARD_NOT_ACTIVE`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed.
        - `status: ACTIVE`: normal authorization behavior resumes.
        - `status: CLOSED`: terminal close. The card transitions to `status: "CLOSED"` with `statusReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. The funding source is detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys.
        - `fundingSource` change: returns the updated card with the new binding and fires no webhook.

        The `card.status_change` webhook fires on every successful `status` transition.
      operationId: updateCardById
      tags:
        - Cards
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardUpdateRequest'
            examples:
              freeze:
                summary: Freeze an active card
                value:
                  status: FROZEN
                  substatus: SUSPICIOUS_ACTIVITY
                  reason: Unrecognised charges reported by the cardholder.
              unfreeze:
                summary: Unfreeze a frozen card
                value:
                  status: ACTIVE
                  substatus: ISSUER_REQUEST
                  reason: Review closed with no action.
              updateFundingSource:
                summary: Replace the card's bound funding source
                value:
                  fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000003
              setSpendingLimit:
                summary: Set the card's spending and transaction limits
                value:
                  maxSpendPerTransaction: 10000
                  maxSpendPerDay: 25000
                  maxTransactionsPerDay: 20
              clearSpendingLimit:
                summary: Remove the card's spending limits
                value:
                  maxSpendPerTransaction: null
                  maxSpendPerDay: null
                  maxTransactionsPerDay: null
              freezeAndUpdateSource:
                summary: Freeze the card and replace its funding source in one call
                value:
                  status: FROZEN
                  substatus: SUSPICIOUS_ACTIVITY
                  reason: Unrecognised charges reported by the cardholder.
                  fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000003
              close:
                summary: Permanently close the card
                value:
                  status: CLOSED
                  substatus: END_USER_REQUEST
                  reason: Cardholder asked us to close the card.
      responses:
        '200':
          description: Card updated. Returns the updated card.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Card'
        '400':
          description: Bad request. Returned with `INVALID_INPUT` when a supplied funding source does not belong to the cardholder or uses a currency unsupported by the card program, when `fundingSource` is combined with any `status` change on a card program where the card issuer makes authorization decisions, when `status` is supplied without `substatus` or without a non-empty `reason`, and for general invalid parameters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized. Returned when the platform credential is missing or invalid, or does not grant access to this card.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Card not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: 'Conflict. Returned with `INVALID_STATE_TRANSITION` when the requested `status` transition is not one of `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, or `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`, or when activation is requested before delegated signing authorization is complete; with `CARD_ALREADY_CLOSED` when `status: CLOSED` is requested for a card that is already `CLOSED`; and with `CARD_NOT_MUTABLE` when the card is `CLOSED`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
  /cards/{id}/reveal:
    parameters:
      - name: id
        in: path
        description: System-generated unique card identifier
        required: true
        schema:
          type: string
    post:
      summary: Reveal card details
      description: |-
        When the card's `cardCapabilities.supportsPanReveal` is true, mint a signed, short-lived URL for the card processor's iframe that displays the card's full PAN, CVV, and expiry to the cardholder. The `Card` resource never carries a reveal URL.

        When `cardCapabilities.supportsPanReveal` is false, this endpoint cannot reveal the card. Use the card issuer's challenge-based hosted reveal flow instead.

        Request the reveal right before rendering the iframe and render the returned `panEmbedUrl` immediately; it expires at `expiresAt` (within minutes). Never store, cache, or log the URL — it is a bearer secret for the full card details. The card data renders inside the processor's iframe and never crosses Grid's or your servers.

        Set `cardConfigs.panRevealCssUrl` on your platform config to style the iframe with your own stylesheet, or send `cssUrl` in the body to override it for one reveal.

        Every reveal is audit-logged with the requesting actor.
      operationId: revealCard
      tags:
        - Cards
      security:
        - BasicAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardRevealRequest'
      responses:
        '200':
          description: Reveal URL minted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CardRevealResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden. The session has no attributable actor to audit the reveal against (for example, an impersonated dashboard session).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The card's `cardCapabilities.supportsPanReveal` is false, so Grid cannot mint a PAN reveal URL for it. Use the card issuer's challenge-based hosted reveal flow instead. This capability is fixed for the card, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
  /cards/{id}/tokenize:
    parameters:
      - name: id
        in: path
        description: System-generated unique card identifier
        required: true
        schema:
          type: string
    post:
      summary: Add a card to a digital wallet
      description: |-
        Exchange the values your app received from Apple Pay, Google Pay, or Samsung Pay for the encrypted provisioning payload that completes adding the card to that wallet (in-app push provisioning). Call it from your backend inside the wallet SDK's credentials callback and hand the response straight back to the SDK.

        The payload is encrypted to the wallet provider's keys, so neither your app nor your servers ever see card data. Do not store or log it; it is valid for one provisioning attempt.

        Cardholders can always add a card to a wallet by typing its details in manually — that path needs nothing from you. This endpoint is for the in-app "Add to Wallet" button, which additionally requires approval from each wallet provider. See [Digital wallet tokenization](/cards/card-management/digital-wallet-tokenization).

        Only `ACTIVE` cards can be added. Every call is audit-logged with the requesting actor and the target wallet.
      operationId: tokenizeCard
      tags:
        - Cards
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardTokenizationRequestOneOf'
            examples:
              applePay:
                summary: Apple Pay
                value:
                  wallet: APPLE_PAY
                  certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM=
                  nonce: MTIzNDU2Nzg=
                  nonceSignature: c2lnbmF0dXJl
              googlePay:
                summary: Google Pay (Unified Push Provisioning)
                value:
                  wallet: GOOGLE_PAY
                  walletAccountId: wallet-account-8f14e45f
                  serverSessionId: 8c3b8a7e-2a5d-4b7f-9c1e-6d2f0a1b3c4d
                  opaquePaymentCardRequested: false
              samsungPay:
                summary: Samsung Pay
                value:
                  wallet: SAMSUNG_PAY
                  walletAccountId: wallet-account-8f14e45f
                  deviceId: device-3c59dc04
      responses:
        '200':
          description: Provisioning payload minted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CardTokenizationResponseOneOf'
              examples:
                applePay:
                  summary: Apple Pay
                  value:
                    wallet: APPLE_PAY
                    applePay:
                      activationData: YWN0aXZhdGlvbi1kYXRh
                      encryptedPassData: ZW5jcnlwdGVkLXBhc3MtZGF0YQ==
                      ephemeralPublicKey: ZXBoZW1lcmFsLXB1YmxpYy1rZXk=
                googlePay:
                  summary: Google Pay
                  value:
                    wallet: GOOGLE_PAY
                    provisioningPayload: eyJjYXJkIjoiLi4uIn0=
        '400':
          description: Invalid input. The body does not match the request shape for its `wallet` (a required field is missing, or a field from another wallet's shape was supplied), or the wallet provider rejected the values (for example, an expired Apple nonce). Restart the add-to-wallet flow in the app to obtain fresh values.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden. The session has no attributable actor to audit the request against (for example, an impersonated dashboard session).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. `CARD_NOT_MUTABLE` when the card is `CLOSED`; `CONFLICT` when the card's `cardCapabilities.supportsDigitalWalletTokenization` is false or the card is not `ACTIVE`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without a configured card issuer return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
  /cards/{id}/set-pin:
    parameters:
      - name: id
        in: path
        description: System-generated unique card identifier
        required: true
        schema:
          type: string
    post:
      summary: Set a card PIN
      description: |-
        Set or replace the card's online PIN using a block encrypted in your client with the published PIN encryption public key. Grid cannot decrypt the block and never stores or logs PIN material.

        Use this when you operate your own PIN-entry UI. For a hosted PIN-entry iframe, use `GET /cards/{id}/pin-entry-url` — the two are alternatives, and a card may use either at any time.

        Setting a PIN clears a `BLOCKED` PIN, so a cardholder locked out by incorrect attempts can recover by choosing a new PIN without a separate unblock. Read `GET /cards/{id}` for the resulting status. Grid supports online PINs only; offline PIN cards are not supported.

        Every call is audit-logged with the requesting actor.
      operationId: setCardPin
      tags:
        - Cards
      security:
        - BasicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardPinSetRequest'
      responses:
        '204':
          description: PIN change accepted.
        '400':
          description: Bad request. Returned with `INVALID_INPUT` when `encryptedPinBlock` is absent, is not valid base64, or the encrypted block is rejected.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden. The session has no attributable actor to audit the change against (for example, an impersonated dashboard session).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. Returned with `CONFLICT` for a closed card or a card that does not support PIN management.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without card support return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
  /cards/{id}/pin-entry-url:
    parameters:
      - name: id
        in: path
        description: System-generated unique card identifier
        required: true
        schema:
          type: string
    get:
      summary: Get a PIN-entry URL
      description: |-
        Get a temporary URL for a secure PIN-entry iframe. The cardholder chooses a four-digit PIN inside the iframe, without the plaintext PIN crossing your servers or Grid's. Grid supports online PINs only.

        Configure `cardConfigs.pinTargetOrigin` through `PATCH /config` before requesting a URL. It is the canonical HTTPS origin of the page that hosts the iframe. The iframe is restricted to that origin; this endpoint takes no request body or origin parameter.

        Each call creates a fresh, temporary PIN-entry session. Request a URL immediately before displaying the form. Fetching the URL does not set or change the PIN. Set your iframe's `src` to the returned `iframeUrl` without modifying it. Follow the [Card PIN guide](https://docs.lightspark.com/cards/card-management/pins) to submit the form and confirm the result. Never store, cache, or log the URL or token. The session expires at `expiresAt` and permits one successful PIN submission. Responses include `Cache-Control: no-store`.

        Use the iframe's submission result to confirm completion, and `GET /cards/{id}` to read `pinStatus`. `OK` alone cannot confirm a change to an already configured PIN.

        If you operate your own PIN-entry UI, use `POST /cards/{id}/set-pin` with a client-encrypted payload instead.

        Requires permission to manage cards. Every successful URL request is audit-logged with the requesting actor.
      operationId: getCardPinUrl
      tags:
        - Cards
      security:
        - BasicAuth: []
      responses:
        '200':
          description: A fresh PIN-entry URL and its expiration.
          headers:
            Cache-Control:
              description: Prevents storage of the temporary PIN-entry credential.
              schema:
                type: string
                enum:
                  - no-store
              example: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CardPinUrlResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden. The caller lacks permission to manage cards or has no attributable actor to audit the request against (for example, an impersonated dashboard session).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. Returned with `CONFLICT` for a closed card, a card that does not support PIN management, or an unset `cardConfigs.pinTargetOrigin`. Configure the origin through `PATCH /config` before requesting a PIN-entry URL.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without card support return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
  /cards/{id}/pin/unblock:
    parameters:
      - name: id
        in: path
        description: System-generated unique card identifier
        required: true
        schema:
          type: string
    post:
      summary: Unblock a card PIN
      description: |-
        Clear a `BLOCKED` PIN, restoring the cardholder's existing PIN without changing it. A PIN blocks after three consecutive incorrect entries; until it is cleared, PIN-verified transactions decline.

        Unblocking is idempotent and safe to call on a card whose `pinStatus` is already `OK`. It has no effect on a card with no PIN set, which returns `409` — set a PIN with `POST /cards/{id}/set-pin` instead.

        Each card permits one unblock attempt per rolling seven days. Further attempts return `409 PIN_CHANGE_REQUIRED` with `details.pinUnblockAvailableAt`. Change the PIN through either PIN-entry flow to recover sooner; changing the PIN does not reset the unblock allowance.

        Only request an unblock after authenticating the cardholder and receiving their explicit request. Do not automatically unblock after a declined transaction.

        Grid supports online PINs only. Offline PIN cards are not supported. Read `GET /cards/{id}` to check `pinStatus`.

        Every call is audit-logged with the requesting actor.
      operationId: unblockCardPin
      tags:
        - Cards
      security:
        - BasicAuth: []
      responses:
        '204':
          description: PIN unblock accepted; read GET /cards/{id} for its `pinStatus`.
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden. The session has no attributable actor to audit the change against (for example, an impersonated dashboard session).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. Returned with `CONFLICT` for a closed card, a card with no PIN to unblock, or a card that does not support PIN management. Returned with `PIN_CHANGE_REQUIRED` when the card's seven-day unblock allowance is consumed. Change the PIN or wait until `details.pinUnblockAvailableAt`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '501':
          description: Not implemented in this environment. Cards are not enabled for every Grid deployment; environments without card support return `501 NOT_IMPLEMENTED`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error501'
  /sandbox/cards/{id}/simulate/authorization:
    post:
      summary: Simulate a card authorization
      description: |
        Simulate an inbound card authorization in the sandbox environment. Drives the same internal `authorize` + `reconcile` paths the card issuer would call in production, so platforms can exercise Grid's decisioning + funding-source pull behavior end-to-end without an external network round-trip.

        The decisioning outcome is controlled by the last three characters of `merchant.descriptor`:

        | Suffix | Outcome | | ------ | ------- | | `002`  | Declined, `cardDeclinedReason: INSUFFICIENT_FUNDS`. Sandbox only: production approves an underfunded authorization and resolves it as `EXCEPTION` when the pull fails | | `003`  | Declined, `cardDeclinedReason: CARD_NOT_ACTIVE`. Use against a `FROZEN` card to verify it refuses authorizations | | `005`  | Delayed pull (~30s) — exercises the `PENDING → CONFIRMED` path | | `006`  | Pull succeeds but the confirmation event reports `FAILED` — exercises the high-urgency `EXCEPTION` alert | | any other | Approved |

        `merchant.descriptor` must be 1–25 characters — the width of the card network's acceptor-name field. A longer one is rejected with `400` `INVALID_INPUT`.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardAuthorization
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card to simulate an authorization against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardAuthorizationRequest'
            examples:
              coffeeAuth:
                summary: Approved $12.50 auth at a coffee shop
                value:
                  amount: 1250
                  currency:
                    code: USD
                  merchant:
                    descriptor: BLUE BOTTLE COFFEE SF
                    mcc: '5814'
                    country: US
              declinedInsufficientFunds:
                summary: Declined — insufficient funds (descriptor suffix `002`)
                value:
                  amount: 50000
                  currency:
                    code: USD
                  merchant:
                    descriptor: AMAZON RETAIL US-002
                    mcc: '5942'
                    country: US
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found (also returned in production for this path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/clearing:
    post:
      summary: Simulate a card clearing
      description: |
        Simulate a clearing (settlement) event against an existing `CardTransaction` in the sandbox environment.

        - A clearing `amount` greater than the authorized amount exercises the over-auth post-hoc-pull path (e.g. restaurant tip on top of a 20% over-auth).
        - A clearing `amount` of `0` exercises the `AUTHORIZATION_EXPIRY` path — the auth expires with no clearing posted.
        - Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds (use the suffix table from `simulate/authorization` to construct deterministic test cases).

        Production returns `404` on this path.
      operationId: sandboxSimulateCardClearing
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the clearing applies to.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardClearingRequest'
            examples:
              tipOnTopClearing:
                summary: Clearing larger than auth — exercises post-hoc pull
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  amount: 1500
              authorizationExpiry:
                summary: Clearing of 0 — exercises authorization expiry
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  amount: 0
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/return:
    post:
      summary: Simulate a card return
      description: |
        Simulate a merchant-initiated `RETURN` against an existing settled card transaction in the sandbox environment. Creates a `CardRefund` and posts the return as its own dated `CREDIT` `CardTransaction` linked to the purchase via `originalTransactionId`; the purchase keeps its `SETTLED` status, whether the return is full or partial.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardReturn
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the return applies to.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardReturnRequest'
            examples:
              fullRefund:
                summary: Full refund of a $15.00 settled transaction
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  amount: 1500
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/balance_inquiry:
    post:
      summary: Simulate a card balance inquiry
      description: |
        Simulate a balance-inquiry authorization against a card in the sandbox environment. A balance inquiry is always a `0`-amount authorization, so the request carries no `amount` — only the `merchant`. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardBalanceInquiry
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card to simulate a balance inquiry against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardBalanceInquiryRequest'
            examples:
              balanceInquiry:
                summary: Balance inquiry at a fuel pump
                value:
                  merchant:
                    descriptor: SHELL OIL 12345678
                    mcc: '5541'
                    country: US
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found (also returned in production for this path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/credit_authorization:
    post:
      summary: Simulate a card credit authorization
      description: |
        Simulate an inbound credit authorization (a merchant-initiated credit to the card, e.g. a refund pushed as an authorization) in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook.

        As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardCreditAuthorization
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card to simulate a credit authorization against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardAuthorizationRequest'
            examples:
              creditAuth:
                summary: $25.00 credit authorization
                value:
                  amount: 2500
                  currency:
                    code: USD
                  merchant:
                    descriptor: ACME MARKETPLACE
                    mcc: '5942'
                    country: US
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found (also returned in production for this path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/financial_authorization:
    post:
      summary: Simulate a card financial authorization
      description: |
        Simulate a single-message financial authorization (an authorization that clears in the same message, e.g. an ATM withdrawal or other dual-message-exempt flow) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook.

        As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardFinancialAuthorization
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card to simulate a financial authorization against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardAuthorizationRequest'
            examples:
              financialAuth:
                summary: $80.00 single-message ATM withdrawal
                value:
                  amount: 8000
                  currency:
                    code: USD
                  merchant:
                    descriptor: ATM WITHDRAWAL 24TH ST
                    mcc: '6011'
                    country: US
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found (also returned in production for this path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/financial_credit_authorization:
    post:
      summary: Simulate a card financial credit authorization
      description: |
        Simulate a single-message financial credit authorization (a credit to the card that clears in the same message, e.g. an ATM deposit or push credit) against a card in the sandbox environment. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook.

        As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardFinancialCreditAuthorization
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card to simulate a financial credit authorization against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardAuthorizationRequest'
            examples:
              financialCreditAuth:
                summary: $40.00 single-message push credit
                value:
                  amount: 4000
                  currency:
                    code: USD
                  merchant:
                    descriptor: ACME PAYOUTS
                    mcc: '6012'
                    country: US
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found (also returned in production for this path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/authorization_advice:
    post:
      summary: Simulate a card authorization advice
      description: |
        Simulate an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization on a `CardTransaction` in the sandbox environment. The `amount` is the new *total* authorized amount the advice re-sizes to. The parent transaction must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state. The resulting card operation is delivered asynchronously via the issuer's events webhook.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardAuthorizationAdvice
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the advice applies to.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardAuthorizationAdviceRequest'
            examples:
              resizeUp:
                summary: Re-size an open auth up to a new total of $20.00
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  amount: 2000
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/credit_authorization_advice:
    post:
      summary: Simulate a card credit authorization advice
      description: |
        Simulate a network-initiated `CREDIT_AUTHORIZATION_ADVICE` against a card in the sandbox environment. Like `simulate/credit_authorization`, it is keyed by the card (PAN) and `merchant` rather than an existing transaction. Drives the same internal paths the card issuer would call in production. The resulting card operation is delivered asynchronously via the issuer's events webhook.

        As with `simulate/authorization`, the decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` suffix table.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardCreditAuthorizationAdvice
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the advice applies to.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardAuthorizationRequest'
            examples:
              creditAdvice:
                summary: $25.00 credit authorization advice
                value:
                  amount: 2500
                  currency:
                    code: USD
                  merchant:
                    descriptor: ACME MARKETPLACE
                    mcc: '5942'
                    country: US
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card not found (also returned in production for this path)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/return_reversal:
    post:
      summary: Simulate a card return reversal
      description: |
        Simulate a `RETURN_REVERSAL` against an existing refund in the sandbox environment — reversing a previously posted return. Pass the refund row's own id (the `CREDIT` `CardTransaction` linked to the purchase via `originalTransactionId`) as `cardTransactionId`. The resulting card operation is delivered asynchronously via the issuer's events webhook.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardReturnReversal
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the return reversal applies to.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardTransactionRefRequest'
            examples:
              returnReversal:
                summary: Reverse a settled return on a transaction with a posted return
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/authorization_reversal:
    post:
      summary: Simulate a card authorization reversal
      description: |
        Simulate a merchant cancelling a card authorization before it settles — a hotel releasing a hold, or a store voiding a purchase that never went through. Use it to see how your integration handles an authorization that disappears without ever clearing.
        Send `amount` to reverse only part of the authorization, or leave it out to reverse all of it. The transaction must still be fully open: once any part of it has settled, it can no longer be reversed.
        Grid processes the reversal in the background. A full reversal sets `status: VOIDED` and sends a `CARD_TRANSACTION.VOIDED` webhook. A partial reversal leaves the transaction `AUTHORIZED` with a smaller hold.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardAuthorizationReversal
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the reversal applies to.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardReversalRequest'
            examples:
              fullReversal:
                summary: Reverse the whole open authorization
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
              partialReversal:
                summary: Reverse part of it and leave the rest authorized
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  amount: 500
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters, or the transaction is not an open authorization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/authorization_expiry:
    post:
      summary: Simulate a card authorization expiry
      description: |
        Simulate an authorization the merchant never followed through on, so the card network lets it lapse and releases the held funds. Use it to see how your integration handles a hold that quietly goes away instead of settling.
        The transaction must still be fully open: once any part of it has settled, it can no longer expire.
        Grid processes the expiry in the background. Once it lands, the transaction has `status: VOIDED` and Grid sends a `CARD_TRANSACTION.VOIDED` webhook.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardAuthorizationExpiry
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the expiry applies to.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardTransactionRefRequest'
            examples:
              expiry:
                summary: Expire an open authorization
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
      responses:
        '202':
          description: Simulation accepted. The resulting card operation is delivered asynchronously via the issuer's events webhook. Returns the issuer transaction token that correlates the simulated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardSimulationResponse'
        '400':
          description: Bad request - Invalid parameters, or the transaction is not an open authorization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/dispute_open:
    post:
      summary: Simulate opening a card dispute
      description: |
        Open a dispute against an existing settled card transaction in the sandbox environment, as the card issuer would when a cardholder files a claim. Creates the dispute with an `OPENED` event on its timeline. The card issuer offers no dispute simulator, so the sandbox delivers the same dispute record the issuer's webhook would.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardDisputeOpen
      x-hidden: true
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the dispute is filed against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardDisputeOpenRequest'
            examples:
              fullDispute:
                summary: Dispute the whole $15.00 settled transaction
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  reason: Merchandise not received
              partialDispute:
                summary: Dispute $4.00 of a larger settled transaction
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  amount: 400
                  reason: Overcharged for one item
      responses:
        '202':
          description: The dispute was opened.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardDisputeResponse'
        '400':
          description: Bad request - Invalid parameters, or the transaction has no settled clearings to dispute
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. Either the transaction already has an open dispute, or the simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/dispute_provisional_credit:
    post:
      summary: Simulate a provisional credit on a card dispute
      description: |
        Instruct a provisional credit to the cardholder on the transaction's open dispute, as the card issuer does while a dispute runs. The credit is paid from the card's funding source when the platform is enabled for dispute funding.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardDisputeProvisionalCredit
      x-hidden: true
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the dispute is filed against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardDisputeCreditRequest'
            examples:
              fullProvisionalCredit:
                summary: Credit the cardholder the whole $15.00 disputed amount
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  amount: 1500
      responses:
        '202':
          description: The credit was instructed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardDisputeResponse'
        '400':
          description: Bad request - Invalid parameters, the transaction has no open dispute, or the amount exceeds the disputed amount
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/dispute_provisional_credit_reversal:
    post:
      summary: Simulate reversing a provisional credit on a card dispute
      description: |
        Instruct a previously granted provisional credit back from the cardholder on the transaction's open dispute, as the card issuer does when the cardholder is found liable.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardDisputeProvisionalCreditReversal
      x-hidden: true
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the dispute is filed against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardDisputeCreditRequest'
            examples:
              fullReversal:
                summary: Take back the whole $15.00 provisional credit
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  amount: 1500
      responses:
        '202':
          description: The reversal was instructed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardDisputeResponse'
        '400':
          description: Bad request - Invalid parameters, the transaction has no open dispute, or the amount exceeds the disputed amount
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /sandbox/cards/{id}/simulate/dispute_close:
    post:
      summary: Simulate closing a card dispute
      description: |
        Close the transaction's open dispute with a disposition, as the card issuer does when the network process ends. A `PARTIALLY_WON` close is the one outcome whose split the caller names, with `recoveredAmount`; every other disposition recovers all of the disputed amount or none of it.

        Production returns `404` on this path.
      operationId: sandboxSimulateCardDisputeClose
      x-hidden: true
      tags:
        - Sandbox
      security:
        - BasicAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: The id of the card the dispute is filed against.
          schema:
            type: string
          example: Card:019542f5-b3e7-1d02-0000-000000000010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxCardDisputeCloseRequest'
            examples:
              wonDispute:
                summary: The cardholder wins the whole disputed amount
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  disposition: WON
              partiallyWonDispute:
                summary: The network gives back $4.00 of a $15.00 dispute
                value:
                  cardTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                  disposition: PARTIALLY_WON
                  recoveredAmount: 400
      responses:
        '202':
          description: The dispute was closed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCardDisputeResponse'
        '400':
          description: Bad request - Invalid parameters, the transaction has no open dispute, or a `PARTIALLY_WON` close named a `recoveredAmount` that is not strictly between 0 and the disputed amount
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden - request was made with a production platform token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Card or card transaction not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict. The simulation targets a card in a program where the card issuer makes authorization decisions. These sandbox simulators support only card programs where Grid makes authorization decisions. This is a property of the card's program, so retrying will not help.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /stablecoins:
    post:
      summary: Register an existing provider-created stablecoin
      description: |
        Register an existing provider-created stablecoin as a Grid `Stablecoin`. This endpoint links provider token metadata to Grid; it does not create the token with the provider. Grid derives the active provider account link from the authenticated platform, provider, and provider environment.
      operationId: registerStablecoin
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          description: Required idempotency key for retrying this request safely.
          required: true
          schema:
            type: string
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StablecoinRegisterRequest'
      responses:
        '201':
          description: Stablecoin registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Stablecoin'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Referenced stablecoin provider account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List stablecoins
      description: Retrieve stablecoins registered to the authenticated platform.
      operationId: listStablecoins
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      parameters:
        - name: issuanceStatus
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/StablecoinIssuanceStatus'
        - name: gridOperationsStatus
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/StablecoinGridOperationsStatus'
        - name: provider
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/StablecoinProvider'
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Opaque cursor returned as `nextCursor` from the previous response.
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StablecoinListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /stablecoins/{stablecoinId}:
    parameters:
      - name: stablecoinId
        in: path
        description: System-generated stablecoin identifier
        required: true
        schema:
          type: string
    get:
      summary: Get a stablecoin
      description: Retrieve a single registered stablecoin by its Grid identifier.
      operationId: getStablecoin
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Stablecoin'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Stablecoin not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /stablecoins/{stablecoinId}/request-grid-enablement:
    parameters:
      - name: stablecoinId
        in: path
        description: System-generated stablecoin identifier
        required: true
        schema:
          type: string
    post:
      summary: Request Grid enablement for a stablecoin
      description: |
        Ask Lightspark to approve a registered stablecoin as a Grid settlement currency. Grid enablement is a reviewed workflow rather than a self-service switch, so this endpoint only records the request: it moves `gridOperationsStatus` from `NOT_ENABLED` to `PENDING_APPROVAL` and returns the updated stablecoin. Lightspark drives the remaining `ENABLING` and `ENABLED` steps.

        The request is idempotent. Repeating it while the stablecoin is already `PENDING_APPROVAL` succeeds and changes nothing. A stablecoin in any other Grid operations status cannot be requested, and returns `409`.
      operationId: requestStablecoinGridEnablement
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Grid enablement requested
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Stablecoin'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: Forbidden. The caller may not request Grid enablement for this stablecoin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '404':
          description: Stablecoin not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: The stablecoin is not in a Grid operations status that can be requested for enablement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /stablecoins/{stablecoinId}/mints:
    parameters:
      - name: stablecoinId
        in: path
        description: System-generated stablecoin identifier
        required: true
        schema:
          type: string
    post:
      summary: Create a stablecoin mint operation
      description: |
        Create a provider-backed mint operation for a registered stablecoin. Funding and destination accounts are normal Grid `ExternalAccount` objects; any provider-specific Brale address linkage is managed internally by Grid. Wire-funded mints return funding instructions and remain pending until the provider reports funds received.
      operationId: createStablecoinMint
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          description: Required idempotency key for retrying this mint request safely.
          required: true
          schema:
            type: string
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StablecoinMintRequest'
      responses:
        '201':
          description: Mint operation created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StablecoinOperation'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Stablecoin or referenced account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '503':
          description: Provider temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error503'
  /stablecoins/{stablecoinId}/burns:
    parameters:
      - name: stablecoinId
        in: path
        description: System-generated stablecoin identifier
        required: true
        schema:
          type: string
    post:
      summary: Create a stablecoin burn operation
      description: |
        Create a provider-backed burn/redemption operation for a registered stablecoin. Burn sources and fiat destinations are normal Grid account objects; provider-specific Brale address ids are resolved and stored internally. External Spark wallet sources return funding instructions and remain pending until the provider reports funds received.
      operationId: createStablecoinBurn
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          description: Required idempotency key for retrying this burn request safely.
          required: true
          schema:
            type: string
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StablecoinBurnRequest'
      responses:
        '201':
          description: Burn operation created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StablecoinOperation'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Stablecoin or referenced account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
        '503':
          description: Provider temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error503'
  /stablecoins/{stablecoinId}/operations:
    parameters:
      - name: stablecoinId
        in: path
        description: System-generated stablecoin identifier
        required: true
        schema:
          type: string
    get:
      summary: List stablecoin issuer operations
      description: List issuer mint and burn operations for a stablecoin, most recent first, with cursor pagination.
      operationId: listStablecoinOperations
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      parameters:
        - name: operationType
          in: query
          description: Filter operations by type. When omitted, both mints and burns are returned.
          required: false
          schema:
            $ref: '#/components/schemas/StablecoinOperationType'
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Opaque cursor returned as `nextCursor` from the previous response.
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StablecoinOperationListResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Stablecoin not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /stablecoin-operations/{stablecoinOperationId}:
    parameters:
      - name: stablecoinOperationId
        in: path
        description: System-generated stablecoin issuer operation identifier
        required: true
        schema:
          type: string
    get:
      summary: Get a stablecoin issuer operation
      description: Retrieve a single stablecoin issuer operation by its Grid identifier.
      operationId: getStablecoinOperation
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StablecoinOperation'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Stablecoin operation not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /stablecoin-provider-accounts:
    post:
      summary: Link a stablecoin provider account
      description: |
        Link provider API credentials for the authenticated platform. Provider credentials are account-scoped and can be reused for multiple stablecoins. Currently, the only supported provider is `BRALE`; the provider environment is derived from the authenticated Grid platform mode.
      operationId: linkStablecoinProviderAccount
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          description: Idempotency key for retrying this request safely. Replays return the prior result; conflicting payloads are rejected.
          required: true
          schema:
            type: string
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StablecoinProviderAccountLinkRequest'
      responses:
        '201':
          description: Stablecoin provider account linked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StablecoinProviderAccount'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    get:
      summary: List stablecoin provider account links
      description: Retrieve stablecoin provider account links for the authenticated platform.
      operationId: listStablecoinProviderAccounts
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      parameters:
        - name: provider
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/StablecoinProvider'
        - name: status
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/StablecoinProviderAccountStatus'
        - name: limit
          in: query
          description: Maximum number of results to return (default 20, max 100)
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: cursor
          in: query
          description: Cursor for pagination (returned from previous request)
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StablecoinProviderAccountListResponse'
        '400':
          description: Bad request - Invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
  /stablecoin-provider-accounts/{stablecoinProviderAccountId}:
    parameters:
      - name: stablecoinProviderAccountId
        in: path
        description: System-generated stablecoin provider account link identifier
        required: true
        schema:
          type: string
    get:
      summary: Get a stablecoin provider account link
      description: Retrieve a stablecoin provider account link by id for the authenticated platform.
      operationId: getStablecoinProviderAccount
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StablecoinProviderAccount'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Stablecoin provider account link not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
    delete:
      summary: Revoke a stablecoin provider account link
      description: Revoke the provider credentials behind a stablecoin provider account link. The stored credential material is cleared and the link's `status` becomes `REVOKED`; the link itself remains readable. Stablecoins registered under a revoked link can no longer mint or burn, and registering a new stablecoin under it is rejected with `STABLECOIN_PROVIDER_ACCOUNT_REVOKED`. Linking the same provider account again reactivates the link with the new credentials. Revoking a link that is already revoked succeeds and changes nothing.
      operationId: revokeStablecoinProviderAccount
      tags:
        - Stablecoins
      security:
        - BasicAuth: []
      responses:
        '204':
          description: Stablecoin provider account link revoked
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Stablecoin provider account link not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
webhooks:
  agent-action:
    post:
      summary: Agent action pending approval webhook
      description: |
        Fired when an agent submits an action that requires platform approval before Grid will execute it. Use this to send a push notification to the customer so they can review and approve or reject the action in your app.
        This endpoint should be implemented by clients of the Grid API.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.

        The payload contains the full `AgentAction` — including the embedded quote or transfer details — so you can render the approval UI without a second API call. Approve or reject via `POST /agents/{agentId}/actions/{actionId}/approve` or `POST /agents/{agentId}/actions/{actionId}/reject`.
      operationId: agentActionWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentActionWebhook'
            examples:
              pendingApproval:
                summary: Agent action pending approval
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000020
                  type: AGENT_ACTION.PENDING_APPROVAL
                  timestamp: '2025-10-03T15:00:00Z'
                  data:
                    id: AgentAction:019542f5-b3e7-1d02-0000-000000000099
                    agentId: Agent:019542f5-b3e7-1d02-0000-000000000042
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000010
                    platformCustomerId: user-a1b2c3
                    status: PENDING_APPROVAL
                    type: EXECUTE_QUOTE
                    quote:
                      id: Quote:019542f5-b3e7-1d02-0000-000000000006
                      status: PENDING
                      expiresAt: '2025-10-03T15:00:30Z'
                      createdAt: '2025-10-03T15:00:00Z'
                      source:
                        sourceType: ACCOUNT
                        accountId: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                      destination:
                        destinationType: ACCOUNT
                        accountId: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                      sendingCurrency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                      receivingCurrency:
                        code: INR
                        name: Indian Rupee
                        symbol: ₹
                        decimals: 2
                      totalSendingAmount: 50000
                      totalReceivingAmount: 4625000
                      exchangeRate: 92.5
                      feesIncluded: 250
                      platformFeesIncluded: 0
                      transactionId: Transaction:019542f5-b3e7-1d02-0000-000000000099
                    createdAt: '2025-10-03T15:00:00Z'
                    updatedAt: '2025-10-03T15:00:00Z'
      responses:
        '200':
          description: Webhook received and acknowledged.
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  incoming-payment:
    post:
      summary: Incoming payment webhook and approval mechanism
      description: |
        Webhook that is called when an incoming payment is received by a customer — over UMA to the customer's UMA address, or over a bank rail (e.g. a SWIFT wire to the customer's funding coordinates from `fundingPaymentInstructions`).
        This endpoint should be implemented by clients of the Grid API.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.

        ### Payment Approval Flow
        When a transaction has `status: "PENDING"`, this webhook serves as an approval mechanism:

        1. The client should check the `counterpartyInformation` against their requirements
        2. To APPROVE the payment synchronously, return a 200 OK response
        3. To REJECT the payment, return a 403 Forbidden response with an Error object
        4. To request more information, return a 422 Unprocessable Entity with specific missing fields
        5. To process the payment asynchronously, return a 202 Accepted response and then call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint within 5 seconds. Note that synchronous approval/rejection is preferred where possible.

        The Grid system will proceed or cancel the payment based on your response.

        For transactions with other statuses (COMPLETED, FAILED, REFUNDED), this webhook is purely informational.

        An ACH deposit can be returned after `COMPLETED`, days after it settled. The return takes the funds back, so the payment moves to `FAILED` and `INCOMING_PAYMENT.FAILED` is sent with `source.railDetails.status` set to `RETURNED` and `returnedAt` set. If the return is dishonored and the funds come back, the payment moves to `COMPLETED` again and `INCOMING_PAYMENT.COMPLETED` is sent with `railDetails.status` set to `RETURN_DISHONORED` and `dishonoredAt` set. A contested dishonor sends `INCOMING_PAYMENT.FAILED` once more with `RETURN_CONTESTED`.

        Bank-rail incoming payments (e.g. SWIFT) are informational at every status, including PENDING: the funds have already moved interbank, so the approval mechanism and the 5-second response window do not apply.
      operationId: incomingPaymentWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IncomingPaymentWebhook'
            examples:
              pendingPayment:
                summary: Pending payment example requiring approval
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000007
                  type: INCOMING_PAYMENT.PENDING
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000005
                    status: PENDING
                    type: INCOMING
                    direction: CREDIT
                    destination:
                      destinationType: UMA_ADDRESS
                      umaAddress: $recipient@uma.domain
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    senderUmaAddress: $sender@external.domain
                    receiverUmaAddress: $recipient@uma.domain
                    receivedAmount:
                      amount: 50000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    counterpartyInformation:
                      FULL_NAME: John Sender
                      BIRTH_DATE: '1985-06-15'
                      NATIONALITY: US
                    reconciliationInstructions:
                      reference: REF-123456789
                    requestedReceiverCustomerInfoFields:
                      - name: NATIONALITY
                        mandatory: true
                      - name: POSTAL_ADDRESS
                        mandatory: false
              incomingCompletedPayment:
                summary: Completed payment notification
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000007
                  type: INCOMING_PAYMENT.COMPLETED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000005
                    status: COMPLETED
                    type: INCOMING
                    direction: CREDIT
                    destination:
                      destinationType: UMA_ADDRESS
                      umaAddress: $recipient@uma.domain
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    senderUmaAddress: $sender@external.domain
                    receiverUmaAddress: $recipient@uma.domain
                    receivedAmount:
                      amount: 50000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    settledAt: '2025-08-15T14:30:00Z'
                    createdAt: '2025-08-15T14:25:18Z'
                    description: Payment for services
                    reconciliationInstructions:
                      reference: REF-123456789
              incomingCompletedCryptoPayment:
                summary: Completed payment funded from an external crypto wallet
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000009
                  type: INCOMING_PAYMENT.COMPLETED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000006
                    status: COMPLETED
                    type: INCOMING
                    direction: CREDIT
                    source:
                      sourceType: REALTIME_FUNDING
                      currency: USDC
                      railDetails:
                        paymentRail: ONCHAIN
                        transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf
                        network: SOLANA
                      onChainTransaction:
                        transactionHash: 7RJWhvQBQPEjJmki5fhBboGBWRJhmcFkMvrr4Fu3tMSJ5EdynMEiYSyiWAH9GpcbHpeUzeSQF9ZY6q4x8AhBskUf
                        network: SOLANA
                    destination:
                      destinationType: ACCOUNT
                      accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    receivedAmount:
                      amount: 100000
                      currency:
                        code: USDC
                        name: USD Coin
                        symbol: ''
                        decimals: 6
                    settledAt: '2025-08-15T14:30:00Z'
                    createdAt: '2025-08-15T14:25:18Z'
                    description: USDC deposit from self-custody wallet
              incomingCompletedWirePayment:
                summary: Completed payment funded by a wire
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000011
                  type: INCOMING_PAYMENT.COMPLETED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000012
                    status: COMPLETED
                    type: INCOMING
                    direction: CREDIT
                    source:
                      sourceType: REALTIME_FUNDING
                      currency: USD
                      railDetails:
                        paymentRail: WIRE
                        status: SETTLED
                        settledAt: '2025-08-15T14:30:00Z'
                        imad: 20250815MMQFMP9C000123
                        originatorToBeneficiaryInformation: INV-12345
                        bankAccount:
                          accountHolderName: John Sender
                          routingNumber: '021000021'
                          accountNumber: '****6789'
                          bankName: Chase Bank
                      paymentRail: WIRE
                      accountHolderName: John Sender
                      accountIdentifier: '****6789'
                      bankName: Chase Bank
                      bankIdentifier: '021000021'
                      remittanceInformation: INV-12345
                      traceNumber: 20250815MMQFMP9C000123
                    destination:
                      destinationType: ACCOUNT
                      accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    receivedAmount:
                      amount: 2500000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    settledAt: '2025-08-15T14:30:00Z'
                    createdAt: '2025-08-15T14:25:18Z'
                    description: Wire deposit
              incomingAchDepositReturned:
                summary: ACH deposit returned after completion
                description: The deposit settled, and the originating bank later returned the ACH entry. The return takes the funds back, so the payment moves to `FAILED` and the source's `railDetails` shows `RETURNED`.
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000013
                  type: INCOMING_PAYMENT.FAILED
                  timestamp: '2025-08-18T15:02:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000014
                    status: FAILED
                    type: INCOMING
                    direction: CREDIT
                    source:
                      sourceType: ACCOUNT
                      accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456
                      currency: USD
                      railDetails:
                        paymentRail: ACH
                        status: RETURNED
                        bankAccount:
                          accountHolderName: Jane Sender
                          routingNumber: '021000021'
                          accountNumber: '****4321'
                        traceNumber: '021000020123456'
                        returnedAt: '2025-08-18T15:00:00Z'
                    destination:
                      destinationType: ACCOUNT
                      accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    receivedAmount:
                      amount: 100000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    settledAt: '2025-08-15T14:30:00Z'
                    createdAt: '2025-08-15T14:25:18Z'
                    description: ACH deposit
              incomingRefundedPayment:
                summary: Refunded incoming payment notification
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000011
                  type: INCOMING_PAYMENT.REFUND_COMPLETED
                  timestamp: '2025-08-15T14:40:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000005
                    status: FAILED
                    type: INCOMING
                    direction: CREDIT
                    destination:
                      destinationType: UMA_ADDRESS
                      umaAddress: $recipient@uma.domain
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    senderUmaAddress: $sender@external.domain
                    receiverUmaAddress: $recipient@uma.domain
                    receivedAmount:
                      amount: 50000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    createdAt: '2025-08-15T14:25:18Z'
                    description: Payment for services
                    reconciliationInstructions:
                      reference: REF-123456789
                    failureReason: OFFRAMP_FAILED
                    refund:
                      reference: REF-123456789-REFUND
                      initiatedAt: '2025-08-15T14:35:00Z'
                      settledAt: '2025-08-15T14:40:00Z'
                      status: COMPLETED
                      reason: TRANSACTION_FAILED
      responses:
        '200':
          description: |
            Webhook received successfully.
            For PENDING transactions on approval-mechanism rails, this indicates approval to proceed with the payment. For bank-rail incoming payments (e.g. SWIFT) a 200 only acknowledges receipt: there is nothing to approve.
            If `requestedReceiverCustomerInfoFields` were present in the webhook request, the corresponding fields for the recipient must be included in this response in the `receiverCustomerInfo` object.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IncomingPaymentWebhookResponse'
        '202':
          description: |
            Webhook received and will be processed asynchronously. The synchronous 200 response should be preferred where possible. This asycnhronous path should only be used in
            cases where the platform's architecture requires async (but still very quick) processing before approving or rejecting the payment.
            The platform must call the `/transactions/{transactionId}/approve` or `/transactions/{transactionId}/reject` endpoint to approve or reject the payment within 5 seconds or the payment will be automatically rejected.
            Bank-rail incoming payments (e.g. SWIFT) need no approve/reject call: a 202 is treated the same as a 200 acknowledgment.
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '403':
          description: |
            Forbidden - Payment rejected by the client.
            Only applicable for PENDING transactions on approval-mechanism rails;
            a bank-rail incoming payment (e.g. SWIFT) cannot be rejected through
            this response because the funds have already moved interbank.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
        '422':
          description: |
            Unprocessable Entity - Additional counterparty information required.
            Only applicable for PENDING transactions on approval-mechanism rails;
            for bank-rail incoming payments (e.g. SWIFT) this response has no
            effect on the already-received funds.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IncomingPaymentWebhookUnprocessableResponse'
  outgoing-payment:
    post:
      summary: Outgoing payment status webhook
      description: |
        Webhook that is called when an outgoing payment's status changes. A side's `railDetails` carries the rail-level status of that leg; for example, a payout the receiving bank returns arrives as `OUTGOING_PAYMENT.FAILED` with `destination.railDetails.status` set to `RETURNED`, and the payout is refunded to its source. A returned payout stays `FAILED` even if the originating bank later dishonors the return: the refund has already been posted, so the funds are reconciled manually. The dishonor is still reported: `OUTGOING_PAYMENT.FAILED` is sent again with `railDetails.status` set to `RETURN_DISHONORED` and `dishonoredAt` set.
        This endpoint should be implemented by clients of the Grid API.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.

        This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded.
      operationId: outgoingPaymentWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutgoingPaymentWebhook'
            examples:
              outgoingCompletedPayment:
                summary: Completed outgoing payment
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000007
                  type: OUTGOING_PAYMENT.COMPLETED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000005
                    status: COMPLETED
                    type: OUTGOING
                    direction: DEBIT
                    source:
                      sourceType: ACCOUNT
                      accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    destination:
                      destinationType: ACCOUNT
                      accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    senderUmaAddress: $sender@uma.domain
                    receiverUmaAddress: $recipient@external.domain
                    sentAmount:
                      amount: 10550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    receivedAmount:
                      amount: 9706
                      currency:
                        code: EUR
                        name: Euro
                        symbol: €
                        decimals: 2
                    exchangeRate: 0.92
                    quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006
                    settledAt: '2025-08-15T14:30:00Z'
                    createdAt: '2025-08-15T14:25:18Z'
                    description: 'Payment for invoice #1234'
                    paymentInstructions: []
                    rateDetails:
                      counterpartyMultiplier: 1.08
                      counterpartyFixedFee: 10
                      gridApiMultiplier: 0.925
                      gridApiFixedFee: 10
                      gridApiVariableFeeRate: 0.003
                      gridApiVariableFeeAmount: 30
              outgoingCompletedCryptoPayment:
                summary: Completed crypto payout to an external wallet
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000008
                  type: OUTGOING_PAYMENT.COMPLETED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000009
                    status: COMPLETED
                    type: OUTGOING
                    direction: DEBIT
                    source:
                      sourceType: ACCOUNT
                      accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    destination:
                      destinationType: ACCOUNT
                      accountId: ExternalAccount:c34dcbd6-dced-4ec4-b756-3c3a9ea3d789
                      railDetails:
                        paymentRail: ONCHAIN
                        transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx
                        network: SOLANA
                      onChainTransaction:
                        transactionHash: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx
                        network: SOLANA
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    sentAmount:
                      amount: 100000
                      currency:
                        code: USDC
                        name: USD Coin
                        symbol: ''
                        decimals: 6
                    receivedAmount:
                      amount: 100000
                      currency:
                        code: USDC
                        name: USD Coin
                        symbol: ''
                        decimals: 6
                    quoteId: Quote:019542f5-b3e7-1d02-0000-000000000010
                    settledAt: '2025-08-15T14:30:00Z'
                    createdAt: '2025-08-15T14:25:18Z'
                    description: USDC withdrawal to self-custody wallet
                    paymentInstructions: []
              outgoingAchPaymentReturned:
                summary: ACH payout returned by the receiving bank
                description: The payout settled, and the receiving bank later returned the ACH entry. The transaction moves to `FAILED` with `PAYOUT_RETURNED`, and the destination's `railDetails` moves to `RETURNED`. The refund back to the source follows as `OUTGOING_PAYMENT.REFUND_PENDING` and `OUTGOING_PAYMENT.REFUND_COMPLETED`.
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000011
                  type: OUTGOING_PAYMENT.FAILED
                  timestamp: '2025-08-18T15:02:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000012
                    status: FAILED
                    type: OUTGOING
                    direction: DEBIT
                    source:
                      sourceType: ACCOUNT
                      accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    destination:
                      destinationType: ACCOUNT
                      accountId: ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456
                      railDetails:
                        paymentRail: ACH
                        status: RETURNED
                        bankAccount:
                          accountHolderName: Jane Receiver
                          routingNumber: '021000021'
                          accountNumber: '****4321'
                        traceNumber: '021000020123456'
                        addenda: INV-2025-0815
                        settledAt: '2025-08-15T14:30:00Z'
                        returnedAt: '2025-08-18T15:00:00Z'
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    sentAmount:
                      amount: 50000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    receivedAmount:
                      amount: 50000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    quoteId: Quote:019542f5-b3e7-1d02-0000-000000000013
                    settledAt: '2025-08-15T14:30:00Z'
                    createdAt: '2025-08-15T14:25:18Z'
                    description: Vendor payout
                    paymentInstructions: []
                    failureReason: PAYOUT_RETURNED
              failedPayment:
                summary: Failed outgoing payment
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000007
                  type: OUTGOING_PAYMENT.FAILED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000005
                    status: FAILED
                    type: OUTGOING
                    direction: DEBIT
                    source:
                      sourceType: ACCOUNT
                      accountId: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    destination:
                      destinationType: ACCOUNT
                      accountId: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    senderUmaAddress: $sender@uma.domain
                    receiverUmaAddress: $recipient@external.domain
                    sentAmount:
                      amount: 10550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    createdAt: '2025-08-15T14:25:18Z'
                    quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006
                    failureReason: QUOTE_EXECUTION_FAILED
      responses:
        '200':
          description: Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  test-webhook:
    post:
      summary: Test webhook for integration verification
      description: |
        Webhook that is sent once to verify your webhook endpoint is correctly set up.
        This is sent when you configure or update your platform settings with a webhook URL.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by the Grid API.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.

        This webhook is purely for testing your endpoint integration and signature verification.
      operationId: testWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TestWebhookRequest'
            examples:
              testWebhook:
                summary: Test webhook example
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000001
                  type: TEST
                  timestamp: '2025-08-15T14:32:00Z'
                  data: {}
      responses:
        '200':
          description: Webhook received successfully. This confirms your webhook endpoint is properly configured.
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  bulk-upload:
    post:
      summary: Bulk upload status webhook
      description: |
        Webhook that is called when a bulk customer upload job completes or fails.
        This endpoint should be implemented by clients of the Grid API.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.

        This webhook is sent when a bulk upload job completes or fails, providing detailed information about the results.
      operationId: bulkUploadWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkUploadWebhook'
            examples:
              completedUpload:
                summary: Successful bulk upload completion
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000008
                  type: BULK_UPLOAD.COMPLETED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Job:019542f5-b3e7-1d02-0000-000000000006
                    status: COMPLETED
                    progress:
                      total: 5000
                      processed: 5000
                      successful: 5000
                      failed: 0
                    errors: []
              failedUpload:
                summary: Failed bulk upload
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000008
                  type: BULK_UPLOAD.FAILED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Job:019542f5-b3e7-1d02-0000-000000000006
                    status: FAILED
                    progress:
                      total: 5000
                      processed: 5000
                      successful: 0
                      failed: 5000
                    errors:
                      - correlationId: row_1
                        error:
                          code: invalid_csv_format
                          message: Invalid CSV format
                          details:
                            reason: missing_required_column
                            column: umaAddress
      responses:
        '200':
          description: Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  invitation-claimed:
    post:
      summary: Invitation claimed webhook
      description: |
        Webhook that is called when an invitation is claimed by a customer.
        This endpoint should be implemented by platform clients of the Grid API.

        When a customer claims an invitation, this webhook is triggered to notify the platform that:
        1. The invitation has been successfully claimed
        2. The invitee UMA address is now associated with the invitation
        3. The invitation status has changed from PENDING to CLAIMED

        This allows platforms to:
        - Track invitation usage and conversion rates
        - Trigger onboarding flows for new customers who joined via invitation
        - Apply referral bonuses or rewards to the inviter
        - Update their UI to reflect the claimed status

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.
      operationId: invitationClaimedWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvitationClaimedWebhook'
            examples:
              claimedInvitation:
                summary: Invitation claimed notification
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000008
                  type: INVITATION.CLAIMED
                  timestamp: '2025-09-01T15:45:00Z'
                  data:
                    code: 019542f5
                    createdAt: '2025-09-01T14:30:00Z'
                    claimedAt: '2025-09-01T15:45:00Z'
                    inviterUma: $inviter@uma.domain
                    inviteeUma: $invitee@uma.domain
                    status: CLAIMED
                    url: https://uma.me/i/019542f5
      responses:
        '200':
          description: Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  customer-update:
    post:
      summary: Customer status change
      description: |
        Webhook that is called when the status of a customer is updated, including KYC and KYB status changes.
        This endpoint should be implemented by clients of the Grid API.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid API public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.
      operationId: customerStatusWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerWebhook'
            examples:
              kycApprovedWebhook:
                summary: When an individual customer KYC has been approved
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000007
                  type: CUSTOMER.KYC_APPROVED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 9f84e0c2a72c4fa
                    customerType: INDIVIDUAL
                    agreementConsents:
                      - type: LIGHTSPARK_END_USER_TERMS
                        acceptedAt: '2025-07-21T17:30:00Z'
                        ipAddress: 203.0.113.42
                        termsVersion: '2025-10-13'
                        acceptanceMethod: CHECKBOX
                    region: US
                    currencies:
                      - USD
                      - USDC
                    umaAddress: $john.doe@uma.domain.com
                    kycStatus: APPROVED
                    fullName: John Michael Doe
                    birthDate: '1990-01-15'
                    nationality: US
                    address:
                      line1: 123 Main Street
                      line2: Apt 4B
                      city: Seattle
                      state: WA
                      postalCode: '98101'
                      country: US
                    createdAt: '2025-07-21T17:32:28Z'
                    updatedAt: '2025-07-21T17:32:28Z'
                    isDeleted: false
              kycRejectedWebhook:
                summary: When an individual customer KYC has been rejected
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000008
                  type: CUSTOMER.KYC_REJECTED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Customer:019542f5-b3e7-1d02-0000-000000000002
                    platformCustomerId: 4b7c1e9d3f5a8e2
                    customerType: INDIVIDUAL
                    agreementConsents:
                      - type: LIGHTSPARK_END_USER_TERMS
                        acceptedAt: '2025-07-21T17:30:00Z'
                        ipAddress: 203.0.113.42
                        termsVersion: '2025-10-13'
                        acceptanceMethod: CHECKBOX
                    region: US
                    currencies:
                      - USD
                      - USDC
                    umaAddress: $jane.smith@uma.domain.com
                    kycStatus: REJECTED
                    fullName: Jane Smith
                    birthDate: '1988-03-22'
                    nationality: US
                    createdAt: '2025-07-21T17:32:28Z'
                    updatedAt: '2025-08-15T14:32:00Z'
                    isDeleted: false
              kybApprovedWebhook:
                summary: When a business customer KYB has been approved
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000009
                  type: CUSTOMER.KYB_APPROVED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Customer:019542f5-b3e7-1d02-0000-000000000003
                    platformCustomerId: 7a2f9d4e1b8c3f5
                    customerType: BUSINESS
                    agreementConsents:
                      - type: LIGHTSPARK_END_USER_TERMS
                        acceptedAt: '2025-07-21T17:30:00Z'
                        ipAddress: 203.0.113.42
                        termsVersion: '2025-10-13'
                        acceptanceMethod: CHECKBOX
                    region: US
                    currencies:
                      - USD
                      - USDC
                    umaAddress: $acme.corp@uma.domain.com
                    kybStatus: APPROVED
                    address:
                      line1: 456 Business Ave
                      city: Seattle
                      state: WA
                      postalCode: '98101'
                      country: US
                    businessInfo:
                      legalName: Acme Corporation
                      registrationNumber: '12345678'
                      taxId: 98-7654321
                      incorporatedOn: '2018-03-14'
                    createdAt: '2025-07-21T17:32:28Z'
                    updatedAt: '2025-08-15T14:32:00Z'
                    isDeleted: false
              kybRejectedWebhook:
                summary: When a business customer KYB has been rejected
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000010
                  type: CUSTOMER.KYB_REJECTED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Customer:019542f5-b3e7-1d02-0000-000000000004
                    platformCustomerId: 3c8e5f2a9d1b7e4
                    customerType: BUSINESS
                    agreementConsents:
                      - type: LIGHTSPARK_END_USER_TERMS
                        acceptedAt: '2025-07-21T17:30:00Z'
                        ipAddress: 203.0.113.42
                        termsVersion: '2025-10-13'
                        acceptanceMethod: CHECKBOX
                    region: US
                    currencies:
                      - USD
                    umaAddress: $globex.inc@uma.domain.com
                    kybStatus: REJECTED
                    address:
                      line1: 789 Corporate Blvd
                      city: Chicago
                      state: IL
                      postalCode: '60601'
                      country: US
                    businessInfo:
                      legalName: Globex Inc
                      taxId: 47-1234567
                      incorporatedOn: '2015-09-01'
                    createdAt: '2025-07-21T17:32:28Z'
                    updatedAt: '2025-08-15T14:32:00Z'
                    isDeleted: false
      responses:
        '200':
          description: |
            Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  internal-account-status:
    post:
      summary: Internal account status webhook
      description: |
        Webhook that is called when the status of an internal account changes. This includes balance updates and may include additional account events in the future.
        This endpoint should be implemented by clients of the Grid API.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.

        ### Event types
        - `INTERNAL_ACCOUNT.BALANCE_UPDATED` — Fired when the balance of an internal account changes. The `data` payload contains the full internal account object.
        - `INTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an internal account changes (e.g., `OPEN` → `FROZEN`). The `data` payload contains the full internal account object.
        - `INTERNAL_ACCOUNT.FUNDING_INSTRUCTIONS_UPDATED` — Fired when the account's `fundingPaymentInstructions` change: new funding coordinates became available (e.g., a SWIFT collection account finished provisioning) or previously served coordinates were withdrawn. The `data` payload contains the full internal account object, including the current `fundingPaymentInstructions`.
      operationId: internalAccountStatusWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InternalAccountStatusWebhook'
            examples:
              balanceDecrease:
                summary: A transaction just cleared a customer account and the balance has decreased
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000007
                  type: INTERNAL_ACCOUNT.BALANCE_UPDATED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    type: INTERNAL_FIAT
                    status: ACTIVE
                    balance:
                      amount: 10000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    totalBalance:
                      amount: 12500
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    fundingPaymentInstructions: []
                    createdAt: '2025-08-01T10:00:00Z'
                    updatedAt: '2025-08-15T14:32:00Z'
              statusUpdated:
                summary: The status of an internal account changed (e.g., frozen by Grid)
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000008
                  type: INTERNAL_ACCOUNT.STATUS_UPDATED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    type: INTERNAL_FIAT
                    status: FROZEN
                    balance:
                      amount: 10000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    totalBalance:
                      amount: 10000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    fundingPaymentInstructions: []
                    createdAt: '2025-08-01T10:00:00Z'
                    updatedAt: '2025-08-15T14:32:00Z'
              fundingInstructionsUpdated:
                summary: A SWIFT collection account finished provisioning and the account gained wire coordinates
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000009
                  type: INTERNAL_ACCOUNT.FUNDING_INSTRUCTIONS_UPDATED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    type: INTERNAL_FIAT
                    status: ACTIVE
                    balance:
                      amount: 10000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    totalBalance:
                      amount: 10000
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    fundingPaymentInstructions:
                      - accountOrWalletInfo:
                          accountType: SWIFT_ACCOUNT
                          country: SG
                          swiftCode: DBSSSGSG
                          bankName: DBS Bank
                          accountNumber: '8850123456'
                          accountHolderName: Acme Exports Pte Ltd
                          paymentRails:
                            - SWIFT
                        instructionsNotes: Payments are attributed automatically by the destination account; no reference code is required.
                    createdAt: '2025-08-01T10:00:00Z'
                    updatedAt: '2025-08-15T14:32:00Z'
      responses:
        '200':
          description: |
            Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  external-account-status:
    post:
      summary: External account status webhook
      description: |
        Webhook that is called whenever the status of an external account changes, for any transition between statuses (`PENDING`, `ACTIVE`, `UNDER_REVIEW`, `INACTIVE`, `PENDING_OWNERSHIP_VERIFICATION`, `UNVERIFIED`) — for example when an account under review becomes active, when ownership verification completes (`PENDING_OWNERSHIP_VERIFICATION` → `ACTIVE`), or when a verification attempt fails (`PENDING_OWNERSHIP_VERIFICATION` → `UNVERIFIED`).
        This endpoint should be implemented by clients of the Grid API.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.

        ### Event types
        - `EXTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an external account changes. The `data` payload contains the full external account object.
      operationId: externalAccountStatusWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExternalAccountStatusWebhook'
            examples:
              verificationSucceeded:
                summary: A wallet account became active after ownership verification
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000042
                  type: EXTERNAL_ACCOUNT.STATUS_UPDATED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7
                    status: ACTIVE
                    currency: USDC
                    ownershipType: FIRST_PARTY
                    accountInfo:
                      accountType: ETHEREUM_WALLET
                      address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
              arbitrumWalletActive:
                summary: An Arbitrum wallet account became active
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000044
                  type: EXTERNAL_ACCOUNT.STATUS_UPDATED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: ExternalAccount:f95dcbd6-dced-4ec4-b756-3c3a9ea3d966
                    customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7
                    status: ACTIVE
                    currency: USDC
                    ownershipType: FIRST_PARTY
                    accountInfo:
                      accountType: ARBITRUM_WALLET
                      address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
              verificationFailed:
                summary: A liveness check failed; a new challenge is needed to retry
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000043
                  type: EXTERNAL_ACCOUNT.STATUS_UPDATED
                  timestamp: '2025-08-15T14:45:00Z'
                  data:
                    id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
                    customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7
                    status: UNVERIFIED
                    currency: USDC
                    ownershipType: FIRST_PARTY
                    accountInfo:
                      accountType: ETHEREUM_WALLET
                      address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
      responses:
        '200':
          description: |
            Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  verification-update:
    post:
      summary: Verification status change
      description: |
        Webhook that is called when a customer's KYC/KYB verification status changes.
        This endpoint should be implemented by clients of the Grid API.

        ### Authentication
        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid API public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.
      operationId: verificationStatusWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerificationWebhook'
            examples:
              approved:
                summary: Verification approved
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000030
                  type: VERIFICATION.APPROVED
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Verification:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    verificationStatus: APPROVED
                    errors: []
                    createdAt: '2025-08-15T14:00:00Z'
              resolveErrors:
                summary: Verification requires action
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000031
                  type: VERIFICATION.RESOLVE_ERRORS
                  timestamp: '2025-08-15T14:32:00Z'
                  data:
                    id: Verification:019542f5-b3e7-1d02-0000-000000000011
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    verificationStatus: RESOLVE_ERRORS
                    errors:
                      - resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001
                        type: MISSING_PROOF_OF_ADDRESS_DOCUMENT
                        acceptedDocumentTypes:
                          - PROOF_OF_ADDRESS
                        reason: Proof of address document is required
                    createdAt: '2025-08-15T14:00:00Z'
      responses:
        '200':
          description: |
            Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  card-status-change:
    post:
      summary: Card status change
      description: |
        Webhook that is called when a card's lifecycle status changes. Fires on `PROCESSING → ACTIVE | PENDING_AUTH`, `ACTIVE ⇄ PENDING_AUTH` as funding sources or delegated signing authorization change, on `PROCESSING → CLOSED (ISSUER_REJECTED)` when issuer provisioning fails, and on every subsequent `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → FROZEN`, and `→ CLOSED` transition.

        This endpoint should be implemented by clients of the Grid API.

        ### Authentication

        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.
      operationId: cardStatusChangeWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardStatusChangeWebhook'
            examples:
              activated:
                summary: Card transitioned from PROCESSING to ACTIVE
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000020
                  type: CARD.STATUS_CHANGE
                  timestamp: '2026-05-08T14:11:00Z'
                  data:
                    id: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCardId: card-emp-001
                    status: ACTIVE
                    brand: VISA
                    form: VIRTUAL
                    last4: '4242'
                    expMonth: 12
                    expYear: 2029
                    fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    maxSpendPerTransaction: 5000
                    maxSpendPerDay: 25000
                    maxTransactionsPerDay: 20
                    currency: USD
                    processorRef: card_b81c2a4f
                    issuerRef: lead_card_7a1b9c3d
                    createdAt: '2026-05-08T14:10:00Z'
                    updatedAt: '2026-05-08T14:11:00Z'
              delegationRequired:
                summary: Provisioned card requires funding authorization
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000020
                  type: CARD.STATUS_CHANGE
                  timestamp: '2026-05-08T14:11:00Z'
                  data:
                    id: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCardId: card-emp-001
                    status: PENDING_AUTH
                    statusReason: DELEGATION_REQUIRED
                    brand: VISA
                    form: VIRTUAL
                    last4: '4242'
                    expMonth: 12
                    expYear: 2029
                    fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    maxSpendPerTransaction: 5000
                    maxSpendPerDay: 25000
                    maxTransactionsPerDay: 20
                    currency: USD
                    processorRef: card_b81c2a4f
                    issuerRef: lead_card_7a1b9c3d
                    createdAt: '2026-05-08T14:10:00Z'
                    updatedAt: '2026-05-08T14:11:00Z'
              issuerRejected:
                summary: Card rejected by issuer during provisioning
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000021
                  type: CARD.STATUS_CHANGE
                  timestamp: '2026-05-08T14:12:00Z'
                  data:
                    id: Card:019542f5-b3e7-1d02-0000-000000000011
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    status: CLOSED
                    statusReason: ISSUER_REJECTED
                    form: VIRTUAL
                    fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    maxSpendPerTransaction: null
                    maxSpendPerDay: null
                    maxTransactionsPerDay: null
                    currency: USD
                    createdAt: '2026-05-08T14:10:00Z'
                    updatedAt: '2026-05-08T14:12:00Z'
              frozen:
                summary: Card frozen by the platform
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000022
                  type: CARD.STATUS_CHANGE
                  timestamp: '2026-05-09T09:00:00Z'
                  data:
                    id: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    status: FROZEN
                    brand: VISA
                    form: VIRTUAL
                    last4: '4242'
                    expMonth: 12
                    expYear: 2029
                    fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    maxSpendPerTransaction: 5000
                    maxSpendPerDay: 25000
                    maxTransactionsPerDay: 20
                    currency: USD
                    createdAt: '2026-05-08T14:10:00Z'
                    updatedAt: '2026-05-09T09:00:00Z'
      responses:
        '200':
          description: |
            Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  card-pin-status-change:
    post:
      summary: Card PIN status change
      description: |-
        Sent when Grid records a change to a card's PIN status, including changes detected after a hosted PIN submission, an API request, or a PIN becoming blocked. The data contains the updated Card resource and its new pinStatus.

        Initial card creation does not trigger this event. Changing an existing PIN while its status remains OK does not trigger it either; use the submission result to confirm that the PIN changed.

        Changes made outside the Grid API are detected asynchronously. The event describes the status Grid observed, not every intermediate PIN status. Delivery can be repeated or arrive out of order. Deduplicate by id and use data.updatedAt to avoid replacing newer card data with an older snapshot. Read pinStatus from GET /cards/{id} to retrieve the card's saved status.

        Verify the X-Grid-Signature header using your Grid webhook public key, as for other Grid webhooks. No PIN, encrypted PIN block, or PIN-entry session credential is included.
      operationId: cardPinStatusChangeWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardPinStatusChangeWebhook'
            examples:
              blocked:
                summary: The card PIN became blocked
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000023
                  type: CARD.PIN_STATUS_CHANGE
                  timestamp: '2026-05-09T10:00:00Z'
                  data:
                    id: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    status: ACTIVE
                    pinStatus: BLOCKED
                    brand: VISA
                    form: VIRTUAL
                    last4: '4242'
                    expMonth: 12
                    expYear: 2029
                    fundingSource: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    maxSpendPerTransaction: null
                    maxSpendPerDay: null
                    maxTransactionsPerDay: null
                    currency: USD
                    createdAt: '2026-05-08T14:10:00Z'
                    updatedAt: '2026-05-09T10:00:00Z'
      responses:
        '200':
          description: Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  card-transaction:
    post:
      summary: Card transaction
      description: |
        Webhook that is called on every lifecycle update of a card `CardTransaction`. Fires when an authorization is approved (`CARD_TRANSACTION.AUTHORIZED`), as clearings settle against it (`CARD_TRANSACTION.PARTIALLY_SETTLED`, `CARD_TRANSACTION.SETTLED`), when the authorization is declined (`CARD_TRANSACTION.DECLINED`), when an authorization is fully reversed or expires (`CARD_TRANSACTION.VOIDED`), and when a transaction settles to the card network but the funding-source pull fails (`CARD_TRANSACTION.EXCEPTION`). The payload carries the full `CardTransaction` resource.

        A merchant return is its own `CardTransaction` row (`direction: CREDIT`) linked to the purchase via `originalTransactionId`, and fires its own `CARD_TRANSACTION.*` deliveries on that row — the purchase row is not re-fired for the return.

        This endpoint should be implemented by clients of the Grid API.

        ### Authentication

        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.
      operationId: cardTransactionWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardTransactionWebhook'
            examples:
              authorized:
                summary: Authorization approved
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000040
                  type: CARD_TRANSACTION.AUTHORIZED
                  timestamp: '2026-05-09T10:00:00Z'
                  data:
                    type: CARD
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000100
                    cardId: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    issuerTransactionToken: lithic_txn_b81c2a4f
                    status: AUTHORIZED
                    direction: DEBIT
                    description: BLUE BOTTLE COFFEE SF
                    merchant:
                      descriptor: BLUE BOTTLE COFFEE SF
                      mcc: '5814'
                      country: US
                    authorizedAmount:
                      amount: 12550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    authorizedAt: '2026-05-09T10:00:00Z'
                    createdAt: '2026-05-09T10:00:00Z'
                    updatedAt: '2026-05-09T10:00:00Z'
              settled:
                summary: All clearings posted
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000041
                  type: CARD_TRANSACTION.SETTLED
                  timestamp: '2026-05-09T15:42:11Z'
                  data:
                    type: CARD
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000100
                    cardId: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    issuerTransactionToken: lithic_txn_b81c2a4f
                    status: SETTLED
                    direction: DEBIT
                    description: BLUE BOTTLE COFFEE SF
                    merchant:
                      descriptor: BLUE BOTTLE COFFEE SF
                      mcc: '5814'
                      country: US
                    authorizedAmount:
                      amount: 12550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    settledAmount:
                      amount: 12550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    authorizedAt: '2026-05-09T10:00:00Z'
                    createdAt: '2026-05-09T10:00:00Z'
                    updatedAt: '2026-05-09T15:42:11Z'
              returned:
                summary: Merchant return posted as its own CREDIT row
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000042
                  type: CARD_TRANSACTION.SETTLED
                  timestamp: '2026-05-10T09:15:00Z'
                  data:
                    type: CARD
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000105
                    cardId: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    issuerTransactionToken: lithic_txn_r55e8c1d
                    status: SETTLED
                    direction: CREDIT
                    originalTransactionId: Transaction:019542f5-b3e7-1d02-0000-000000000100
                    description: BLUE BOTTLE COFFEE SF
                    merchant:
                      descriptor: BLUE BOTTLE COFFEE SF
                      mcc: '5814'
                      country: US
                    authorizedAmount:
                      amount: 12550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    settledAmount:
                      amount: 12550
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    authorizedAt: '2026-05-10T09:15:00Z'
                    createdAt: '2026-05-10T09:15:00Z'
                    updatedAt: '2026-05-10T09:15:00Z'
              declined:
                summary: Authorization declined for insufficient funds
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000044
                  type: CARD_TRANSACTION.DECLINED
                  timestamp: '2026-05-09T16:10:00Z'
                  data:
                    type: CARD
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000102
                    cardId: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    issuerTransactionToken: lithic_txn_d41a7f02
                    status: DECLINED
                    cardDeclinedReason: INSUFFICIENT_FUNDS
                    direction: DEBIT
                    description: UBER EATS
                    merchant:
                      descriptor: UBER EATS
                      mcc: '5812'
                      country: US
                    authorizedAmount:
                      amount: 4200
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    authorizedAt: '2026-05-09T16:10:00Z'
                    createdAt: '2026-05-09T16:10:00Z'
                    updatedAt: '2026-05-09T16:10:00Z'
              voided:
                summary: Authorization fully reversed before clearing
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000045
                  type: CARD_TRANSACTION.VOIDED
                  timestamp: '2026-05-09T16:20:00Z'
                  data:
                    type: CARD
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000103
                    cardId: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    issuerTransactionToken: lithic_txn_e52b8c13
                    status: VOIDED
                    direction: DEBIT
                    description: HOTEL AUTHORIZATION
                    merchant:
                      descriptor: HOTEL AUTHORIZATION
                      mcc: '7011'
                      country: US
                    authorizedAmount:
                      amount: 0
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    authorizedAt: '2026-05-09T15:50:00Z'
                    createdAt: '2026-05-09T15:50:00Z'
                    updatedAt: '2026-05-09T16:20:00Z'
              exception:
                summary: Settled to the network but the funding-source pull failed
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000043
                  type: CARD_TRANSACTION.EXCEPTION
                  timestamp: '2026-05-09T16:05:00Z'
                  data:
                    type: CARD
                    id: Transaction:019542f5-b3e7-1d02-0000-000000000101
                    cardId: Card:019542f5-b3e7-1d02-0000-000000000010
                    customerId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    platformCustomerId: 18d3e5f7b4a9c2
                    issuerTransactionToken: lithic_txn_c92d3b5a
                    status: EXCEPTION
                    direction: DEBIT
                    description: UBER EATS
                    merchant:
                      descriptor: UBER EATS
                      mcc: '5812'
                      country: US
                    authorizedAmount:
                      amount: 4200
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    settledAmount:
                      amount: 4200
                      currency:
                        code: USD
                        name: United States Dollar
                        symbol: $
                        decimals: 2
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    authorizedAt: '2026-05-09T15:30:00Z'
                    createdAt: '2026-05-09T15:30:00Z'
                    updatedAt: '2026-05-09T16:05:00Z'
      responses:
        '200':
          description: |
            Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
  wallet-operation:
    post:
      summary: Wallet operation completed or failed
      description: |
        Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. The payload is self-contained — handle it from the fields below with no follow-up API call needed.

        The specific operation is carried in `data.operationType` (`auth_credential.create`, `auth_credential.delete`, `session.revoke`, `wallet.export`, or `customer.contact_update`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`:

        | `operationType` | `resourceType` | `resourceId` identifies |
        | --- | --- | --- |
        | `auth_credential.create` | `AUTH_METHOD` | the created credential — its id can't be known before this webhook, since the create request has nothing to echo |
        | `auth_credential.delete` | `AUTH_METHOD` | the deleted credential |
        | `session.revoke` | `SESSION` | the revoked session |
        | `wallet.export` | `INTERNAL_ACCOUNT` | the account whose wallet was exported |
        | `customer.contact_update` | `CUSTOMER` | the customer whose email or phone number changed |

        The webhook carries no sensitive result material — for `wallet.export`, the export bundle itself is never delivered here; retrieve it by resubmitting the original signed export request until it returns the result.

        ### Correlating this webhook

        - **`data.requestId`** is the primary correlation key. It is the same `Request-Id` value you supplied on the signed retry that produced this terminal result — echo it against the `Request-Id` you sent (and, if you polled through one or more `200 { status: "PROCESSING" }` responses, against the value you kept resending).
        - **`data.resourceId`** is the primary correlator for the *business resource* itself, distinct from `requestId`'s role of matching the request. For `auth_credential.create` this is the only way to learn the created credential's id — the request that created it had nothing to echo.
        - **`id`** (the top-level webhook envelope id) is for deduplication. Grid may redeliver a webhook after a transient failure; track `id` to avoid double-processing.
        - **`data.operationId`** is a Grid-internal identifier for the operation. It's useful when contacting support about a specific operation, but isn't intended as a correlation key.

        This endpoint should be implemented by clients of the Grid API.

        ### Authentication

        The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. To verify the signature:

        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.
      operationId: walletOperationWebhook
      tags:
        - Webhooks
      security:
        - WebhookSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WalletOperationWebhook'
            examples:
              completed:
                summary: Wallet export completed
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000040
                  type: WALLET_OPERATION.COMPLETED
                  timestamp: '2026-06-08T14:31:00Z'
                  data:
                    operationId: Operation:019542f5-b3e7-1d02-0000-000000000099
                    requestId: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45
                    operationType: wallet.export
                    resourceType: INTERNAL_ACCOUNT
                    resourceId: InternalAccount:019542f5-b3e7-1d02-0000-000000000005
                    status: completed
              failed:
                summary: Session revoke failed terminally
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000041
                  type: WALLET_OPERATION.FAILED
                  timestamp: '2026-06-08T14:32:00Z'
                  data:
                    operationId: Operation:019542f5-b3e7-1d02-0000-00000000009a
                    requestId: Request:3c1e5a2f-8b44-4d7a-9e10-6f2b8c4d1a90
                    operationType: session.revoke
                    resourceType: SESSION
                    resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a
                    status: failed
                    error:
                      code: SIGNER_PROVIDER_REJECTED
              contactUpdateCompleted:
                summary: Customer contact change completed
                value:
                  id: Webhook:019542f5-b3e7-1d02-0000-000000000042
                  type: WALLET_OPERATION.COMPLETED
                  timestamp: '2026-06-08T14:33:00Z'
                  data:
                    operationId: Operation:019542f5-b3e7-1d02-0000-00000000009b
                    requestId: Request:019542f5-b3e7-1d02-0000-000000000010
                    operationType: customer.contact_update
                    resourceType: CUSTOMER
                    resourceId: Customer:019542f5-b3e7-1d02-0000-000000000001
                    status: completed
      responses:
        '200':
          description: Webhook received successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized - Signature validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '409':
          description: Conflict - Webhook has already been processed (duplicate id)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error409'
components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
      description: API token authentication using format `<api token id>:<api client secret>`
    AgentAuth:
      type: http
      scheme: bearer
      description: 'Bearer token authentication for agent-scoped endpoints. The token is the `accessToken` returned when redeeming a device code via `POST /agents/device-codes/{code}/redeem`. Agent credentials are user-scoped: all requests are automatically bound to the agent''s associated customer and subject to the agent''s policy.'
    WebhookSignature:
      type: apiKey
      in: header
      name: X-Grid-Signature
      description: |
        Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used to verify that the webhook was sent by Grid.
        To verify the signature:
        1. Get the Grid public key provided to you during integration
        2. Decode the base64 signature from the header
        3. Create a SHA-256 hash of the request body
        4. Verify the signature using the public key and the hash

        If the signature verification succeeds, the webhook is authentic. If not, it should be rejected.
  schemas:
    CustomerInfoFieldName:
      type: string
      enum:
        - FULL_NAME
        - BIRTH_DATE
        - NATIONALITY
        - PHONE_NUMBER
        - EMAIL
        - POSTAL_ADDRESS
        - TAX_ID
        - REGISTRATION_NUMBER
        - USER_TYPE
        - COUNTRY_OF_RESIDENCE
        - ACCOUNT_IDENTIFIER
        - FI_LEGAL_ENTITY_NAME
        - FI_ADDRESS
        - PURPOSE_OF_PAYMENT
        - ULTIMATE_INSTITUTION_COUNTRY
        - IDENTIFIER
        - BUSINESS_TYPE
        - COMPANY_LEGAL_NAME
        - ID_TYPE
        - ID_NUMBER
      description: Name of a type of field containing info about a platform's customer or counterparty customer.
      example: FULL_NAME
    CounterpartyFieldDefinition:
      type: object
      properties:
        name:
          $ref: '#/components/schemas/CustomerInfoFieldName'
        mandatory:
          type: boolean
          description: Whether the field is mandatory
          example: true
      required:
        - name
        - mandatory
    TransactionType:
      type: string
      enum:
        - INCOMING
        - OUTGOING
        - CARD
      description: Type of transaction
    PlatformCurrencyConfig:
      type: object
      properties:
        currencyCode:
          type: string
          description: Three-letter currency code (ISO 4217)
          example: USD
        minAmount:
          type: integer
          format: int64
          description: Minimum amount that can be sent in the smallest unit of this currency
          minimum: 0
          example: 100
        maxAmount:
          type: integer
          format: int64
          description: Maximum amount that can be sent in the smallest unit of this currency
          minimum: 0
          example: 1000000
        requiredCounterpartyFields:
          type: array
          items:
            $ref: '#/components/schemas/CounterpartyFieldDefinition'
          description: List of fields which the platform requires from the counterparty institutions about counterparty customers. Platforms can set mandatory to false if the platform does not require the field, but would like to have it available. Some fields may be required by the underlying UMA provider.
          example:
            - name: FULL_NAME
              mandatory: true
            - name: BIRTH_DATE
              mandatory: true
            - name: NATIONALITY
              mandatory: true
        providerRequiredCustomerFields:
          type: array
          items:
            $ref: '#/components/schemas/CustomerInfoFieldName'
          description: List of customer info field names that are required by the underlying UMA provider when creating a customer for this currency. These fields must be supplied when creating or updating a customer if this currency is intended to be used by that customer. If no fields are required, this field is omitted.
          readOnly: true
          example:
            - NATIONALITY
            - BIRTH_DATE
        providerRequiredCounterpartyCustomerFields:
          type: array
          items:
            $ref: '#/components/schemas/CustomerInfoFieldName'
          description: List of fields that are required by the underlying UMA provider for this currency. If the counterparty does not provide these fields, quote requests will fail.
          readOnly: true
          example:
            - FULL_NAME
            - COUNTRY_OF_RESIDENCE
        enabledTransactionTypes:
          type: array
          items:
            $ref: '#/components/schemas/TransactionType'
          description: List of transaction types that are enabled for this currency.
          example:
            - OUTGOING
            - INCOMING
      required:
        - currencyCode
        - minAmount
        - maxAmount
        - requiredCounterpartyFields
        - enabledTransactionTypes
    EmbeddedWalletConfig:
      type: object
      description: |
        Per-platform embedded-wallet configuration. Controls branding and OTP
        behavior for the email sent when a customer authenticates with an
        EMAIL_OTP credential. Fields omitted from a request are left unchanged.
      properties:
        appName:
          type: string
          maxLength: 255
          description: App name displayed in the default OTP email template.
          example: Acme Wallet
        otpLength:
          type: integer
          minimum: 4
          maximum: 12
          description: |
            Number of digits / characters in the OTP code. Defaults to 6 when
            not set.
          example: 6
        alphanumeric:
          type: boolean
          description: |
            If true, OTP includes letters in addition to digits. Defaults to
            numeric-only when not set.
          example: false
        expirationSeconds:
          type: integer
          minimum: 1
          maximum: 86400
          description: |
            OTP validity window in seconds. Defaults to 300 when not set.
          example: 300
        sendFromEmailAddress:
          type: string
          format: email
          maxLength: 255
          description: Custom sender email address for OTP emails.
          example: noreply@acme.com
        sendFromEmailSenderName:
          type: string
          maxLength: 255
          description: |
            Custom sender display name. Defaults to "Notifications" when not set.
          example: Acme Notifications
        replyToEmailAddress:
          type: string
          format: email
          maxLength: 255
          description: Custom reply-to email address for OTP emails.
          example: support@acme.com
        logoUrl:
          type: string
          format: uri
          maxLength: 512
          description: URL to a PNG logo for the OTP email. Resized to 340x124px.
          example: https://acme.com/logo.png
    CardTokenization2FAEmailConfig:
      type: object
      description: Email branding and sender settings for card-tokenization authentication messages. Invalid or unverified sender identities can cause delivery to fail.
      properties:
        fromAddress:
          type: string
          format: email
          maxLength: 255
          description: Sender address for card-tokenization authentication emails.
          example: cards@acme.com
        fromName:
          type: string
          maxLength: 255
          description: Sender display name. Defaults to `displayName`.
          example: Acme Cards
        replyToAddress:
          type: string
          format: email
          maxLength: 255
          description: Reply-to address for card-tokenization authentication emails.
          example: support@acme.com
        subject:
          type: string
          maxLength: 255
          description: |
            Subject for the authentication email. Defaults to
            "Your {displayName} verification code".
          example: Your Acme card verification code
        bodyText:
          type: string
          maxLength: 2000
          description: |
            Plain-text message content. Lightspark inserts the authentication code into
            a controlled text and HTML template; arbitrary HTML and template variables
            are not supported. Defaults to "Use this code to finish adding your
            {displayName} card to your wallet."
          example: Use this code to finish adding your Acme card to your digital wallet.
    CardTokenization2FASmsConfig:
      type: object
      description: SMS settings for card-tokenization authentication messages delivered through a Lightspark-managed Twilio sender.
      properties:
        templateSid:
          type: string
          maxLength: 64
          description: |
            Twilio Verify template SID to use for this platform. An invalid or unavailable
            template can cause delivery to fail.
          example: HJaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
        bodyText:
          type: string
          maxLength: 480
          description: |
            Plain-text fallback message used when Twilio Verify is unavailable for the
            authentication code. Lightspark appends the code to this text. Defaults to
            "Your {displayName} verification code is:".
          example: Use this code to finish adding your Acme card to your digital wallet.
    CardTokenization2FAConfig:
      type: object
      description: Per-platform branding for authentication codes sent when a cardholder adds a Grid-issued card to a digital wallet. Updates apply to subsequent delivery attempts. Invalid sender or provider configuration can cause delivery to fail.
      properties:
        displayName:
          type: string
          maxLength: 255
          description: |
            Name shown in authentication messages and used to build the default
            subject, body, and sender name. Defaults to the platform's name.
          example: Acme
        logoUrl:
          type: string
          format: uri
          pattern: ^https://
          maxLength: 512
          description: HTTPS URL of the logo displayed in email messages.
          example: https://acme.com/card-email-logo.png
        email:
          $ref: '#/components/schemas/CardTokenization2FAEmailConfig'
        sms:
          $ref: '#/components/schemas/CardTokenization2FASmsConfig'
    CardConfig:
      type: object
      description: Platform-level settings that apply to cards issued by the platform.
      properties:
        pinTargetOrigin:
          anyOf:
            - type: string
              format: uri
              pattern: ^https://(?:[^\s/?#@:\[\]\\]+|\[[0-9A-Fa-f:.]+\])(?::[0-9]{1,5})?$
            - type: 'null'
          description: |-
            Canonical HTTPS origin of the page that hosts the PIN-entry iframe returned by `GET /cards/{id}/pin-entry-url`. Include the scheme, host, and optional port, without a path, trailing slash, query, fragment, or credentials. The iframe is restricted to this origin, so it must match the embedding page's origin exactly. Different user-specific paths on the same origin use this one setting.

            Required before requesting a PIN-entry URL. Omit this field when updating platform configuration to preserve its value; set it to `null` to clear it. If unset, PIN-entry URL requests return `409 CONFLICT`. Changes apply to newly requested URLs.
          example: https://app.example.com
        maxSpendPerTransaction:
          anyOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: 'null'
          description: The largest amount any of your cards can authorize on a single transaction, in the smallest unit of each card's currency (cents for USD). An authorization for exactly the limit is allowed. A later clearing can still settle above it — a restaurant tip, for example — so this caps the authorization, not the final settled amount. It covers every card Grid makes authorization decisions for, both those you have already issued and any you issue later, and does not apply to cards whose issuer makes the decisions. Where a card sets its own `maxSpendPerTransaction`, the lower of the two applies. `null` means no platform-wide limit.
          example: 10000
        maxSpendPerDay:
          anyOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: 'null'
          description: Platform-level cap on cumulative new spend during one UTC calendar day for every card whose authorization decisions are made by Grid. The value is interpreted in the smallest unit of each card's currency. Grid enforces the lower of this cap and the card's configured `maxSpendPerDay`; null means no platform-level daily cap. The window resets at 00:00 UTC. Refunds, reversals, and authorization expiries do not restore capacity during the day. The cap applies to existing cards and cards issued later. Provider-decided card programs are unaffected.
          example: 50000
        maxTransactionsPerDay:
          anyOf:
            - type: integer
              format: int32
              minimum: 1
              maximum: 2147483647
            - type: 'null'
          description: Platform-level cap on the number of transactions each card may authorize during one UTC calendar day, for every card whose authorization decisions are made by Grid. Grid enforces the lower of this cap and the card's configured `maxTransactionsPerDay`; null means no platform-level daily transaction cap. The window resets at 00:00 UTC. Each approved authorization counts once; refunds, reversals, and authorization expiries do not restore capacity during the day. The cap applies to existing cards and cards issued later. Provider-decided card programs are unaffected.
          example: 50
        panRevealCssUrl:
          anyOf:
            - type: string
              format: uri
              pattern: ^https://[^@]*$
              maxLength: 512
            - type: 'null'
          description: HTTPS URL of a stylesheet that styles the card-details iframe returned by `POST /cards/{id}/reveal`, so the revealed PAN, expiry, and CVV carry your branding instead of the default. The card processor's page links the stylesheet and the cardholder's browser fetches it, so it must be reachable over HTTPS without credentials. Style the `card`, `pan`, `expiry`, and `cvv` ids and the `pan-separator` class; the rest of the page structure is not a contract. The stylesheet loads into the page rendering the live card details, so host it somewhere you would trust with them. `null` restores the default styling. A `cssUrl` in the body of `POST /cards/{id}/reveal` overrides this for one reveal. Applies to cards whose `cardCapabilities.supportsPanReveal` is true; cards revealed through the card issuer's own hosted flow are styled by that issuer.
          example: https://acme.com/card-reveal.css
    FeeType:
      type: string
      enum:
        - CROSS_CURRENCY_TRANSACTION
        - RAIL
      description: |
        The kind of activity this fee applies to.

        - `CROSS_CURRENCY_TRANSACTION` — a fee charged on transactions where the sending
          currency differs from the receiving currency
        - `RAIL` — a fee charged on any transaction that uses the specified payment rail
      example: CROSS_CURRENCY_TRANSACTION
    FixedFee:
      type: object
      description: Fixed fee charged per transaction.
      required:
        - amount
        - currency
      properties:
        amount:
          type: integer
          format: int64
          minimum: 0
          maximum: 9007199254740991
          description: Fee amount in the smallest unit of the fixed fee's `currency` (e.g., cents for USD).
          example: 100
        currency:
          type: string
          description: Three-letter currency code (ISO 4217) the fixed fee is denominated in. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.)
          example: USD
    FeeConfig:
      type: object
      description: A platform-configured fee collected by Grid and settled to the platform internal account. These fields are shared by every platform fee config.
      properties:
        feeType:
          $ref: '#/components/schemas/FeeType'
        variableFeeBps:
          type: integer
          minimum: 0
          maximum: 10000
          description: Variable fee in basis points (1 bps = 0.01%) to apply to a transaction's source-currency amount.
          example: 30
        fixedFee:
          $ref: '#/components/schemas/FixedFee'
      required:
        - feeType
        - variableFeeBps
        - fixedFee
    CrossCurrencyTransactionFeeConfig:
      allOf:
        - $ref: '#/components/schemas/FeeConfig'
        - type: object
          properties:
            feeType:
              enum:
                - CROSS_CURRENCY_TRANSACTION
            sourceCurrency:
              type: string
              description: Currency code of the sending side this fee applies to. Only `USD` is accepted today; other currencies return a `NOT_IMPLEMENTED` error.
              example: USD
          required:
            - sourceCurrency
      description: A fee charged for a cross-currency transaction. There can be at most one cross-currency transaction fee config for a given source currency. The fee will apply to all cross-currency transactions that originate in the source currency.
    PaymentRail:
      type: string
      enum:
        - ACH
        - ACH_COLOMBIA
        - ACH_SAME_DAY
        - BANK_TRANSFER
        - BRE_B
        - CIPS
        - FAST
        - FASTER_PAYMENTS
        - FEDNOW
        - INSTAPAY
        - MOBILE_MONEY
        - NEFT
        - PAYNOW
        - PESONET
        - PIX
        - RTGS
        - RTP
        - SEPA
        - SEPA_INSTANT
        - SPEI
        - SWIFT
        - UNIONPAY
        - UPI
        - WIRE
      description: |-
        The payment rail used for the transfer. Payment rails represent the underlying payment network or system used to move funds between accounts.

        `ACH_SAME_DAY` requests same-business-day settlement for a USD payout and is priced separately. It is subject to the NACHA per-entry same-day limit, which the network applies to every originator: $1,000,000 per entry, rising to $10,000,000 on 2027-09-17. A payout above that limit is rejected rather than slowed — check the amount before requesting this rail, or send it over `ACH`, which settles on the standard schedule and has no such limit.

        `ACH` will settle on the standard next-business-day schedule. Until a date we announce in advance, `ACH` continues to settle same-business-day on production platforms; it already settles next-business-day on sandbox platforms. To guarantee same-day settlement after that date, request `ACH_SAME_DAY`.
      example: ACH
    RailFeeConfig:
      allOf:
        - $ref: '#/components/schemas/FeeConfig'
        - type: object
          properties:
            feeType:
              enum:
                - RAIL
            rail:
              $ref: '#/components/schemas/PaymentRail'
              description: The rail used in the transaction. `ACH`, `ACH_SAME_DAY`, `RTP`, `FEDNOW` and `WIRE` are accepted today; other rails return a `NOT_IMPLEMENTED` error.
            direction:
              type: string
              enum:
                - IN
                - OUT
              description: Whether the money is coming in or going out, relative to Grid. Only `OUT` is accepted today.
              example: OUT
            originator:
              type: string
              enum:
                - GRID
                - EXTERNAL
              description: Who originated the movement over the rail. An ACH OUT where Grid is the originator means Grid is pushing out the funds, while an ACH OUT with an EXTERNAL originator indicates Grid received an ACH pull request from an external source.
              example: GRID
          required:
            - rail
            - direction
            - originator
      description: 'A fee charged when a transaction uses the specified rail. There can be at most one for a given rail, direction and originator. For example, a config with rail: ACH, direction: OUT, originator: GRID will charge a fee on all outgoing ACH transactions. `ACH` and `ACH_SAME_DAY` are separate rails, so price each one with its own config.'
    FeeConfigOneOf:
      oneOf:
        - $ref: '#/components/schemas/CrossCurrencyTransactionFeeConfig'
        - $ref: '#/components/schemas/RailFeeConfig'
      discriminator:
        propertyName: feeType
        mapping:
          CROSS_CURRENCY_TRANSACTION: '#/components/schemas/CrossCurrencyTransactionFeeConfig'
          RAIL: '#/components/schemas/RailFeeConfig'
    PlatformConfig:
      type: object
      properties:
        id:
          type: string
          description: System-generated unique identifier
          example: PlatformConfig:019542f5-b3e7-1d02-0000-000000000003
        umaDomain:
          type: string
          pattern: ^[^\u0000-\u001f\u007f]*$
          maxLength: 200
          description: UMA domain for this platform
          example: platform.uma.domain
        proxyUmaSubdomain:
          type: string
          pattern: ^[^\u0000-\u001f\u007f]*$
          maxLength: 200
          description: The subdomain that incoming requests will be proxied to
          example: platform
        webhookEndpoint:
          type: string
          description: URL where webhook notifications will be sent
          example: https://api.mycompany.com/webhooks/uma
        supportedCurrencies:
          type: array
          items:
            $ref: '#/components/schemas/PlatformCurrencyConfig'
          description: |
            List of currencies supported by the platform. This is what the platform's
            customers are able to hold, send, and receive.
        isRegulatedFinancialInstitution:
          type: boolean
          description: |
            Whether the platform is a regulated financial institution. This is used to
            determine if the platform's customers must be KYC/KYB'd by Lightspark via
            the KYC link flow. This can only be set by Lightspark during platform
            creation.
          example: false
        embeddedWalletConfig:
          $ref: '#/components/schemas/EmbeddedWalletConfig'
          description: |
            Embedded-wallet branding and OTP settings for this platform. Present
            only when the platform has configured embedded-wallet support;
            omitted otherwise.
        cardTokenization2faConfig:
          $ref: '#/components/schemas/CardTokenization2FAConfig'
          description: |
            Branding and sender configuration for card-tokenization authentication
            messages. This configuration is independent of embedded-wallet support.
        cardConfigs:
          $ref: '#/components/schemas/CardConfig'
          description: Platform-level settings for cards issued by this platform.
        feeConfigs:
          type: array
          items:
            $ref: '#/components/schemas/FeeConfigOneOf'
          description: |
            Platform-collected fees that should be added on top of
            Grid-collected fees. Contains every currently-active fee config
            for the platform.
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp
          example: '2025-06-15T12:30:45Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp
          example: '2025-06-15T12:30:45Z'
    Error401:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | UNAUTHORIZED | Issue with API credentials |
            | INVALID_SIGNATURE | Signature header is invalid |
            | WALLET_SIGNATURE_MISSING | The `Grid-Wallet-Signature` header is required for this Embedded Wallet action but was not supplied |
            | WALLET_SIGNATURE_MALFORMED | The `Grid-Wallet-Signature` header could not be parsed (bad encoding, structure, or fields) |
            | WALLET_SIGNATURE_BODY_MISMATCH | The `Grid-Wallet-Signature` was computed over a different request body than the one received |
            | WALLET_SIGNATURE_INVALID | The `Grid-Wallet-Signature` failed cryptographic verification against the registered credential |
            | REQUEST_ID_MISSING | The `Request-Id` header is required on the signed retry but was not supplied (paired with `Grid-Wallet-Signature`) |
          enum:
            - UNAUTHORIZED
            - INVALID_SIGNATURE
            - WALLET_SIGNATURE_MISSING
            - WALLET_SIGNATURE_MALFORMED
            - WALLET_SIGNATURE_BODY_MISMATCH
            - WALLET_SIGNATURE_INVALID
            - REQUEST_ID_MISSING
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error500:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | GRID_SWITCH_ERROR | Grid switch error |
            | INTERNAL_ERROR | Internal server or UMA error |
          enum:
            - GRID_SWITCH_ERROR
            - INTERNAL_ERROR
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    PlatformConfigUpdateRequest:
      type: object
      properties:
        umaDomain:
          type: string
          pattern: ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$
          maxLength: 200
          example: mycompany.com
        webhookEndpoint:
          type: string
          example: https://api.mycompany.com/webhooks/uma
        supportedCurrencies:
          type: array
          items:
            $ref: '#/components/schemas/PlatformCurrencyConfig'
        embeddedWalletConfig:
          $ref: '#/components/schemas/EmbeddedWalletConfig'
          description: |
            Update or create the embedded-wallet configuration for this platform.
            Fields omitted from the nested object are left unchanged. Omit this
            field at the top level to leave the embedded-wallet configuration
            unchanged entirely.
        cardTokenization2faConfig:
          $ref: '#/components/schemas/CardTokenization2FAConfig'
          description: |
            Update card-tokenization authentication branding and delivery settings.
            Fields omitted from the nested object are left unchanged. Changes apply
            to subsequent delivery attempts.
        cardConfigs:
          $ref: '#/components/schemas/CardConfig'
          description: Update platform-level card settings. Fields omitted from the nested object are left unchanged. For any card limit, supply null to clear the platform cap or a positive integer to set it. Set `pinTargetOrigin` to configure the website that hosts PIN-entry iframes, or supply null to clear it. Omit this field at the top level to leave all card settings unchanged.
        feeConfigs:
          type: array
          items:
            $ref: '#/components/schemas/FeeConfigOneOf'
          description: |
            Merge-by-key upsert of platform fee configs. A cross-currency transaction
            fee config is keyed by `(feeType, sourceCurrency)` and a rail fee config by
            `(feeType, rail, direction, originator)`. Setting variable and fixed fees
            to 0 for an existing config deactivates it. Omit this field to leave fee
            configs unchanged.
    FieldConstraint:
      type: object
      description: Machine-readable validator hint accompanying a 400 `INVALID_INPUT` error. Consumers use it to drive form UI (input types, dropdowns, masking, length limits) and to pre-validate the field client-side before re-submitting. Fields are additive.
      properties:
        format:
          type: string
          description: Named format the value must satisfy — HTML5 input type names (`email`, `tel`, `url`, `date`, ...) or semantic slugs (`iso3166-1-alpha-2`, `bcp47-language-tag`, `us-ssn`, `e.164`).
          example: email
        pattern:
          type: string
          description: Regular expression the value must match (JavaScript-flavor).
          example: ^\d{5}(-\d{4})?$
        enum:
          type: array
          items:
            type: string
          description: Allowed values when the field is drawn from a fixed set.
          example:
            - SSN
            - ITIN
            - NON_US_TAX_ID
        minLength:
          type: integer
          description: Minimum length in characters.
          example: 1
        maxLength:
          type: integer
          description: Maximum length in characters.
          example: 500
    FieldError:
      type: object
      required:
        - field
      description: One field-level validation failure. Field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`) emit an array of these under `details.errors` so platforms can render form-field-level UX for every failure in a single round-trip.
      properties:
        field:
          type: string
          description: Dot-notation path to the offending field.
          example: identifier
        constraint:
          $ref: '#/components/schemas/FieldConstraint'
        message:
          type: string
          description: Human-readable explanation of what's wrong with this field.
          example: Value is not one of the allowed enum members.
    PaymentDocumentRequirementId:
      type: string
      description: |-
        A stable identifier for one required document within a purpose's requirements. The same ID means the same requirement across quotes. It is separate from `PaymentDocumentType`. The requirement ID names the document to supply, and the document type names what a file is. The two differ where a requirement accepts alternatives. For example, a file declared as either `PURCHASE_ORDER` or `DELIVERY_SLIP` fills the `SUPPORTING_PROOF` requirement.

        [Supporting Documents](https://docs.lightspark.com/payouts-and-b2b/payment-flow/send-payment#supporting-documents) lists the IDs each purpose requires.

        Treat the ID as an opaque value, not a member of a fixed list. Grid may add new IDs as requirements change.
      example: SUPPORTING_PROOF
    Error400:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | INVALID_INPUT | Invalid input provided |
            | END_USER_TERMS_VERSION_NOT_FOUND | The submitted version is not supported for the agreement it was sent for |
            | MISSING_MANDATORY_USER_INFO | Required customer information is missing |
            | INVITATION_ALREADY_CLAIMED | Invitation has already been claimed |
            | INVITATIONS_NOT_CONFIGURED | Invitations are not configured |
            | INVALID_UMA_ADDRESS | UMA address format is invalid |
            | INVITATION_CANCELLED | Invitation has been cancelled |
            | INVALID_RECEIVER | Receiver is invalid |
            | PARSE_PAYREQ_RESPONSE_ERROR | Error parsing receiver PayReq response |
            | CERT_CHAIN_INVALID | Counterparty certificate chain is invalid |
            | CERT_CHAIN_EXPIRED | Counterparty certificate chain has expired |
            | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid |
            | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing |
            | SENDER_NOT_ACCEPTED | Sender is not accepted |
            | AMOUNT_OUT_OF_RANGE | Amount is out of range |
            | INVALID_CURRENCY | Currency is invalid |
            | INVALID_TIMESTAMP | Timestamp is invalid |
            | INVALID_NONCE | Nonce is invalid |
            | INVALID_REQUEST_FORMAT | Request format is invalid |
            | INVALID_BANK_ACCOUNT | Bank account is invalid |
            | SELF_PAYMENT | Self payment not allowed |
            | PARSE_LNURLP_RESPONSE_ERROR | Error parsing LNURLP response |
            | INVALID_AMOUNT | Amount is invalid |
            | WEBHOOK_ENDPOINT_NOT_SET | Webhook endpoint is not set |
            | WEBHOOK_DELIVERY_ERROR | Webhook delivery error |
            | LOW_QUALITY | Document quality too low to process |
            | DATA_MISMATCH | Document details don't match provided information |
            | EXPIRED | Document has expired |
            | SUSPECTED_FRAUD | Document suspected of being forged or edited |
            | UNSUITABLE_DOCUMENT | Document type is not accepted or not supported |
            | INCOMPLETE | Document is missing pages or sides |
            | EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS | An EMAIL_OTP credential is already registered on the target internal account; only one email OTP credential is supported per internal account at this time |
            | SMS_OTP_CREDENTIAL_ALREADY_EXISTS | An SMS_OTP credential is already registered on the target internal account; only one SMS OTP credential is supported per internal account at this time |
            | PASSKEY_CREDENTIAL_ALREADY_EXISTS | A PASSKEY credential with the same WebAuthn credentialId is already registered on the target internal account |
            | AUTH_CREDENTIAL_VERIFICATION_REQUIRED | The new email or phone number has no verified replacement credential |
            | AUTH_CREDENTIAL_VERIFICATION_EXPIRED | The replacement credential's verification has expired |
            | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable |
            | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked |
            | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one |
            | CARDHOLDER_KYC_NOT_APPROVED | The cardholder's KYC status is not `APPROVED`, so a card cannot be issued |
            | TRANSACTION_SIZE_LIMIT_EXCEEDED | The requested amount exceeds the configured maximum single-transaction amount for this trade corridor or withdrawal currency |
            | EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed |
            | INSUFFICIENT_FUNDS | Insufficient funds for this operation |
            | QUOTE_EXPIRED | The quote has expired; request a new quote |
            | QUOTE_RATE_UNAVAILABLE | No exchange rate is available for this corridor right now |
            | STABLECOIN_AMOUNT_NOT_REPRESENTABLE | The amount cannot be represented at the token's precision |
            | STABLECOIN_BURN_SOURCE_NOT_SUPPORTED | The burn source account cannot be used for this operation |
            | STABLECOIN_EXTERNAL_ACCOUNT_LINK_FAILED | Linking the external account for stablecoin operations failed |
            | STABLECOIN_EXTERNAL_ACCOUNT_LINK_METHOD_REQUIRED | The external account needs a link method before it can be used |
            | STABLECOIN_EXTERNAL_ACCOUNT_NOT_LINKED | The external account is not linked for stablecoin operations |
            | STABLECOIN_EXTERNAL_ACCOUNT_NOT_SUPPORTED | This external account type is not supported for stablecoin operations |
            | STABLECOIN_EXTERNAL_ACCOUNT_PROVIDER_LINK_FAILED | The provider could not link the external account |
            | STABLECOIN_EXTERNAL_ACCOUNT_PROVIDER_LINK_REQUIRED | The external account must be linked with the provider first |
            | STABLECOIN_GRID_OPERATIONS_NOT_ENABLED | The stablecoin is not enabled for Grid operations |
            | STABLECOIN_NOT_PROVISIONED | The stablecoin is not provisioned for issuer operations |
            | STABLECOIN_OPERATION_NOT_SUPPORTED | The stablecoin does not support this operation |
            | STABLECOIN_PROVIDER_ERROR | The stablecoin provider rejected the operation |
            | STABLECOIN_PROVIDER_SOURCE_NOT_LINKED | The provider source account is not linked |
            | STABLECOIN_VERIFICATION_FAILED | The stablecoin could not be verified with the provider |
            | DOCUMENTS_REQUIRED | The destination requires supporting documents for the quote's `purposeOfPayment` that the request did not supply. `details.missingRequirements` lists the requirement ID of each one |
          enum:
            - INVALID_INPUT
            - END_USER_TERMS_VERSION_NOT_FOUND
            - MISSING_MANDATORY_USER_INFO
            - INVITATION_ALREADY_CLAIMED
            - INVITATIONS_NOT_CONFIGURED
            - INVALID_UMA_ADDRESS
            - INVITATION_CANCELLED
            - INVALID_RECEIVER
            - PARSE_PAYREQ_RESPONSE_ERROR
            - CERT_CHAIN_INVALID
            - CERT_CHAIN_EXPIRED
            - INVALID_PUBKEY_FORMAT
            - MISSING_REQUIRED_UMA_PARAMETERS
            - SENDER_NOT_ACCEPTED
            - AMOUNT_OUT_OF_RANGE
            - INVALID_CURRENCY
            - INVALID_TIMESTAMP
            - INVALID_NONCE
            - INVALID_REQUEST_FORMAT
            - INVALID_BANK_ACCOUNT
            - SELF_PAYMENT
            - PARSE_LNURLP_RESPONSE_ERROR
            - INVALID_AMOUNT
            - WEBHOOK_ENDPOINT_NOT_SET
            - WEBHOOK_DELIVERY_ERROR
            - LOW_QUALITY
            - DATA_MISMATCH
            - EXPIRED
            - SUSPECTED_FRAUD
            - UNSUITABLE_DOCUMENT
            - INCOMPLETE
            - EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS
            - SMS_OTP_CREDENTIAL_ALREADY_EXISTS
            - PASSKEY_CREDENTIAL_ALREADY_EXISTS
            - AUTH_CREDENTIAL_VERIFICATION_REQUIRED
            - AUTH_CREDENTIAL_VERIFICATION_EXPIRED
            - STABLECOIN_PROVIDER_ACCOUNT_INVALID
            - STABLECOIN_PROVIDER_ACCOUNT_REVOKED
            - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED
            - CARDHOLDER_KYC_NOT_APPROVED
            - TRANSACTION_SIZE_LIMIT_EXCEEDED
            - EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED
            - INSUFFICIENT_FUNDS
            - QUOTE_EXPIRED
            - QUOTE_RATE_UNAVAILABLE
            - STABLECOIN_AMOUNT_NOT_REPRESENTABLE
            - STABLECOIN_BURN_SOURCE_NOT_SUPPORTED
            - STABLECOIN_EXTERNAL_ACCOUNT_LINK_FAILED
            - STABLECOIN_EXTERNAL_ACCOUNT_LINK_METHOD_REQUIRED
            - STABLECOIN_EXTERNAL_ACCOUNT_NOT_LINKED
            - STABLECOIN_EXTERNAL_ACCOUNT_NOT_SUPPORTED
            - STABLECOIN_EXTERNAL_ACCOUNT_PROVIDER_LINK_FAILED
            - STABLECOIN_EXTERNAL_ACCOUNT_PROVIDER_LINK_REQUIRED
            - STABLECOIN_GRID_OPERATIONS_NOT_ENABLED
            - STABLECOIN_NOT_PROVISIONED
            - STABLECOIN_OPERATION_NOT_SUPPORTED
            - STABLECOIN_PROVIDER_ERROR
            - STABLECOIN_PROVIDER_SOURCE_NOT_LINKED
            - STABLECOIN_VERIFICATION_FAILED
            - DOCUMENTS_REQUIRED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details. Shape varies by `code`. For field-validation errors on submit endpoints (e.g. `POST /customers`, `PATCH /customers/{id}`), `details.errors[]` enumerates every invalid field so platforms can render form-field-level UX for the entire request in a single round-trip.
          properties:
            errors:
              type: array
              description: One entry per invalid field. Present on field-validation errors from submit endpoints.
              items:
                $ref: '#/components/schemas/FieldError'
            missingRequirements:
              type: array
              description: The requirement ID of each required document that the quote request did not supply. Present on `DOCUMENTS_REQUIRED`.
              items:
                $ref: '#/components/schemas/PaymentDocumentRequirementId'
          additionalProperties: true
    Error501:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY | Unrecognized mandatory payee data key |
            | NOT_IMPLEMENTED | Feature not implemented |
            | UNRECOGNIZED_MANDATORY_COUNTERPARTY_DATA_KEY | The counterparty requires a data key Grid does not recognise |
          enum:
            - UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY
            - NOT_IMPLEMENTED
            - UNRECOGNIZED_MANDATORY_COUNTERPARTY_DATA_KEY
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Currency:
      type: object
      properties:
        code:
          type: string
          description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.)
          example: USD
        name:
          type: string
          description: Full name of the currency
          example: United States Dollar
        symbol:
          type: string
          description: Symbol of the currency
          example: $
        decimals:
          type: integer
          description: Number of decimal places for the currency
          minimum: 0
          example: 2
    ExchangeRateFees:
      type: object
      description: Fees associated with an exchange rate
      properties:
        fixed:
          type: integer
          format: int64
          description: Fixed fee in the smallest unit of the sending currency (e.g., cents for USD)
          minimum: 0
          example: 100
        total:
          type: integer
          format: int64
          description: Total fees in the smallest unit of the sending currency (e.g., cents for USD). This value may change depending on the sending amount used; if no sending amount is specified, it falls back to the default.
          minimum: 0
          example: 100
    ExchangeRate:
      type: object
      description: Exchange rate information for a currency corridor
      required:
        - sourceCurrency
        - destinationCurrency
        - destinationPaymentRail
        - minSendingAmount
        - maxSendingAmount
        - sendingAmount
        - receivingAmount
        - exchangeRate
        - fees
        - updatedAt
      properties:
        sourceCurrency:
          $ref: '#/components/schemas/Currency'
        sendingAmount:
          type: integer
          format: int64
          description: The sending amount in the smallest unit of the source currency (e.g., cents for USD). Echoed back from the request if provided.
          minimum: 0
          example: 10000
        minSendingAmount:
          type: integer
          format: int64
          description: The minimum supported sending amount in the smallest unit of the source currency.
          minimum: 0
          example: 100
        maxSendingAmount:
          type: integer
          format: int64
          description: The maximum supported sending amount in the smallest unit of the source currency.
          minimum: 0
          example: 10000000
        destinationCurrency:
          $ref: '#/components/schemas/Currency'
        destinationPaymentRail:
          allOf:
            - $ref: '#/components/schemas/PaymentRail'
            - description: The payment rail used for the destination (e.g., UPI, SEPA_INSTANT, MOBILE_MONEY, FASTER_PAYMENTS)
              example: UPI
        receivingAmount:
          type: integer
          format: int64
          description: The receiving amount in the smallest unit of the destination currency
          minimum: 0
          example: 1650000
        exchangeRate:
          type: number
          description: 'Number of sending currency units per receiving currency unit. The rate is fee-exclusive: it covers the currency conversion only, and Grid charges `fees` separately rather than pricing them into the rate.'
          exclusiveMinimum: 0
          example: 0.012121
        fees:
          $ref: '#/components/schemas/ExchangeRateFees'
        updatedAt:
          type: string
          format: date-time
          description: Timestamp when this exchange rate was last refreshed
          example: '2025-02-05T12:00:00Z'
    ExchangeRateListResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          description: List of exchange rates matching the filter criteria
          items:
            $ref: '#/components/schemas/ExchangeRate'
    DiscoveryListResponse:
      type: object
      properties:
        data:
          type: array
          description: List of payment rails matching the filter criteria
          items:
            type: object
            required:
              - bankName
              - displayName
              - country
              - currency
            properties:
              bankName:
                type: string
                description: |
                  Canonical name for this payment rail. Pass this value as `bankName` when creating an external account.
              displayName:
                type: string
                description: Human-friendly display name for this payment rail.
              country:
                type: string
                description: ISO 3166-1 alpha-2 country code.
              currency:
                type: string
                description: ISO 4217 currency code.
    CustomerType:
      type: string
      enum:
        - INDIVIDUAL
        - BUSINESS
      description: Whether the customer is an individual or a business entity
      example: INDIVIDUAL
    AgreementAcceptanceMethod:
      type: string
      description: Method the customer used to affirmatively accept an agreement.
      enum:
        - CHECKBOX
        - CLICK_TO_ACCEPT
      example: CHECKBOX
    EndUserTermsConsent:
      type: object
      deprecated: true
      required:
        - acceptedAt
        - ipAddress
        - termsVersion
        - acceptanceMethod
      description: Deprecated; use `agreementConsents` instead, which records acceptance of each agreement separately. Reported only for a customer whose acceptance predates that field, and always as the `LIGHTSPARK_END_USER_TERMS` agreement.
      properties:
        acceptedAt:
          type: string
          format: date-time
          description: Date and time when the customer accepted the End User Terms.
        ipAddress:
          type: string
          maxLength: 45
          description: IP address of the device the customer used when accepting the terms.
          example: 198.51.100.24
        termsVersion:
          type: string
          description: Version identifier of the accepted Grid End User Terms.
          example: V1
        acceptanceMethod:
          $ref: '#/components/schemas/AgreementAcceptanceMethod'
    AgreementType:
      type: string
      description: 'Identifies which Grid agreement a consent record or document refers to. Values are stable identifiers: a document''s hosted URL or version may change, but its type does not. Accepting one agreement never implies acceptance of another, even when two agreements share a hosted page. The cardholder and accountholder agreements differ by customer kind: an `INDIVIDUAL` customer accepts the `_CONSUMER_` variant, and a `BUSINESS` customer accepts the `_COMMERCIAL_` one.'
      enum:
        - LIGHTSPARK_END_USER_TERMS
        - LIGHTSPARK_E_SIGN_CONSENT
        - LIGHTSPARK_PRIVACY_POLICY
        - LEAD_E_SIGN_CONSENT
        - LEAD_ACCOUNTHOLDER_CONSUMER_AGREEMENT
        - LEAD_ACCOUNTHOLDER_COMMERCIAL_AGREEMENT
        - LEAD_PRIVACY_POLICY
        - LEAD_CARDHOLDER_CONSUMER_AGREEMENT
        - LEAD_CARDHOLDER_COMMERCIAL_AGREEMENT
      example: LIGHTSPARK_END_USER_TERMS
    AgreementConsentRequest:
      type: object
      required:
        - type
        - acceptedAt
        - ipAddress
        - termsVersion
        - acceptanceMethod
      properties:
        type:
          $ref: '#/components/schemas/AgreementType'
        acceptedAt:
          type: string
          format: date-time
          description: Date and time when the customer accepted this agreement. Must include a timezone offset and must not be in the future.
        ipAddress:
          type: string
          maxLength: 45
          description: IP address of the device the customer used when accepting this agreement.
          example: 198.51.100.24
        termsVersion:
          type: string
          maxLength: 64
          description: Version identifier of the accepted agreement, as returned for this type by the agreement documents endpoint. A version is scoped to its type; a version valid for one agreement is not valid for another.
          example: '2025-10-13'
        acceptanceMethod:
          $ref: '#/components/schemas/AgreementAcceptanceMethod'
    AgreementConsent:
      allOf:
        - $ref: '#/components/schemas/AgreementConsentRequest'
    ContactVerificationStatus:
      type: string
      description: Status of an individual contact-verification channel (email or phone). `PENDING` means verification is required but not yet completed; `VERIFIED` means the channel has been confirmed.
      enum:
        - PENDING
        - VERIFIED
      example: VERIFIED
    ContactVerification:
      type: object
      description: |-
        Email and/or phone verification state for the customer. This object is **only present when the customer's regulatory jurisdiction requires contact verification** (e.g. EU customers). For customers who have no such requirement, this object is omitted entirely — no action is needed.

        Each channel is reported independently: only the channels the customer's provider actually requires are present. A provider may require both email and phone, just one of them, or — when the object is absent — neither. Every channel that **is** present must reach `VERIFIED` before the customer can begin KYC. Drive each present channel with `POST /customers/{customerId}/verify-email` and/or `POST /customers/{customerId}/verify-phone` (and their `/confirm` sub-routes).
      properties:
        email:
          $ref: '#/components/schemas/ContactVerificationStatus'
          description: Verification status of the customer's email address. Present only when the provider requires email verification.
        phone:
          $ref: '#/components/schemas/ContactVerificationStatus'
          description: Verification status of the customer's phone number. Present only when the provider requires phone verification.
    Customer:
      type: object
      required:
        - umaAddress
        - platformCustomerId
        - customerType
      properties:
        id:
          type: string
          description: System-generated unique identifier
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        platformCustomerId:
          type: string
          description: Platform-specific customer identifier
          example: 9f84e0c2a72c4fa
        customerType:
          $ref: '#/components/schemas/CustomerType'
        endUserTermsConsent:
          deprecated: true
          readOnly: true
          allOf:
            - $ref: '#/components/schemas/EndUserTermsConsent'
          description: Deprecated; read `agreementConsents` instead. Mirrors the customer's `LIGHTSPARK_END_USER_TERMS` acceptance when one is on file, and is omitted otherwise.
        agreementConsents:
          type: array
          readOnly: true
          description: The customer's recorded agreement acceptances, one entry per accepted type holding that type's most recent acceptance. Omitted when the customer has not accepted any agreement yet.
          items:
            $ref: '#/components/schemas/AgreementConsent'
        region:
          type: string
          description: Country code (ISO 3166-1 alpha-2) representing the customer's regional identity and regulatory jurisdiction.
          example: US
        currencies:
          type: array
          items:
            type: string
          description: List of currency codes enabled for this customer.
          example:
            - USD
            - USDC
        email:
          type: string
          format: email
          description: Email address for the customer.
          example: john.doe@example.com
        phoneNumber:
          type: string
          pattern: ^\+[1-9]\d{1,14}$
          description: Phone number for the customer in strict E.164 format.
          example: '+14155551234'
        umaAddress:
          type: string
          description: Full UMA address (always present in responses, even if system-generated). This is an optional identifier to route payments to the customer.
          example: $john.doe@uma.domain.com
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp
          example: '2025-07-21T17:32:28Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp
          example: '2025-07-21T17:32:28Z'
        isDeleted:
          type: boolean
          description: Whether the customer is marked as deleted
          example: false
        contactVerification:
          allOf:
            - $ref: '#/components/schemas/ContactVerification'
          description: Email and phone verification state. **Only present when the customer's payment provider requires it** (e.g. EU customers); omitted otherwise.
    KycStatus:
      type: string
      enum:
        - UNVERIFIED
        - PENDING
        - APPROVED
        - REJECTED
        - HOLD
      description: The current KYC status of a customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information.
      example: APPROVED
    Address:
      type: object
      required:
        - line1
        - postalCode
        - country
      properties:
        line1:
          type: string
          description: Street address line 1
          example: 123 Main Street
        line2:
          type: string
          description: Street address line 2
          example: Apt 4B
        city:
          type: string
          description: City
          example: San Francisco
        state:
          type: string
          description: State/Province/Region
          example: CA
        postalCode:
          type: string
          description: Postal/ZIP code
          example: '94105'
        country:
          type: string
          description: Country code (ISO 3166-1 alpha-2)
          example: US
    IdentificationType:
      type: string
      enum:
        - SSN
        - ITIN
        - EIN
        - NON_US_TAX_ID
      description: Type of tax identification
      example: SSN
    IndividualSourceOfFundsCategory:
      type: string
      enum:
        - SALARY
        - SELF_EMPLOYMENT_INCOME
        - INVESTMENT_INCOME
        - PENSION
        - RENTAL_INCOME
        - GIFT
        - INHERITANCE
        - LOAN
        - SAVINGS
        - SALE_OF_ASSETS
        - OTHER
      description: A structured source-of-funds category for an individual customer (FLOW of funds into this account). Distinct from `SourceOfFundsCategory`, which covers business customers.
      example: SALARY
    SourceOfWealthCategory:
      type: string
      enum:
        - SALARY
        - BUSINESS_INCOME
        - INVESTMENTS
        - INHERITANCE
        - PROPERTY_SALE
        - GIFT
        - RETIREMENT
        - SAVINGS
        - OTHER
      description: Origin of the customer's accumulated wealth (STOCK). Distinct from `SourceOfFundsCategory` which describes the FLOW of funds for this specific account.
      example: SALARY
    PurposeOfAccount:
      type: string
      enum:
        - CONTRACTOR_PAYOUTS
        - CREATOR_PAYOUTS
        - EMPLOYEE_PAYOUTS
        - MARKETPLACE_SELLER_PAYOUTS
        - SUPPLIER_PAYMENTS
        - CROSS_BORDER_B2B
        - AR_AUTOMATION
        - AP_AUTOMATION
        - EMBEDDED_PAYMENTS
        - PLATFORM_FEE_COLLECTION
        - P2P_TRANSFERS
        - CHARITABLE_DONATIONS
        - OTHER
      description: The intended purpose for using the Grid account
      example: CONTRACTOR_PAYOUTS
    ExpectedMonthlyTransactionCount:
      type: string
      enum:
        - COUNT_UNDER_10
        - COUNT_10_TO_100
        - COUNT_100_TO_500
        - COUNT_500_TO_1000
        - COUNT_OVER_1000
      description: Expected number of transactions per month
      example: COUNT_100_TO_500
    ExpectedMonthlyTransactionVolume:
      type: string
      enum:
        - VOLUME_UNDER_10K
        - VOLUME_10K_TO_100K
        - VOLUME_100K_TO_1M
        - VOLUME_1M_TO_10M
        - VOLUME_OVER_10M
      description: Expected total transaction volume per month in USD equivalent
      example: VOLUME_100K_TO_1M
    AnnualIncomeRange:
      type: string
      enum:
        - UNDER_50K
        - RANGE_50K_100K
        - RANGE_100K_250K
        - RANGE_250K_1M
        - OVER_1M
      description: Bucketed annual income (USD equivalent). Used for enhanced due diligence on higher-risk profiles.
      example: RANGE_100K_250K
    NetWorthRange:
      type: string
      enum:
        - UNDER_100K
        - RANGE_100K_500K
        - RANGE_500K_1M
        - RANGE_1M_5M
        - RANGE_5M_25M
        - OVER_25M
      description: Bucketed total net worth (USD equivalent). Used for enhanced due diligence on higher-risk profiles.
      example: RANGE_500K_1M
    PepStatus:
      type: string
      enum:
        - NONE
        - DOMESTIC
        - FOREIGN
        - HIO
        - FAMILY_OR_ASSOCIATE
      description: Political exposure declaration (Politically Exposed Person status). `HIO` = head of an international organization. `FAMILY_OR_ASSOCIATE` covers close family members and known close associates of a PEP.
      example: NONE
    CustomerEdd:
      type: object
      description: Enhanced-due-diligence (EDD) fields available as optional patchable attributes on an individual customer. Referenced via `allOf` from `IndividualCustomerFields`, so these appear as top-level optional fields on the customer resource itself; there is no separate EDD resource. The specific set required for a given customer is driven by the KYC provider's per-jurisdiction / per-flow / per-volume-tier rules (surfaced through `MISSING_FIELD` errors on `POST /verifications`).
      properties:
        sourceOfFundsCategories:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/IndividualSourceOfFundsCategory'
          description: Structured source-of-funds categories (FLOW of funds for this account).
          example:
            - SALARY
        sourceOfFundsOtherDescription:
          type: string
          maxLength: 500
          description: Free-form description of the customer's source of funds. Required when `sourceOfFundsCategories` includes `OTHER`; otherwise omitted.
          example: Contest winnings
        sourceOfWealthCategories:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/SourceOfWealthCategory'
          description: Structured source-of-wealth categories (STOCK — origin of accumulated wealth).
          example:
            - SALARY
            - INVESTMENTS
        sourceOfWealthOtherDescription:
          type: string
          maxLength: 500
          description: Free-form description of the customer's source of wealth. Required when `sourceOfWealthCategories` includes `OTHER`; otherwise omitted.
          example: Royalty income from published works
        purposeOfAccount:
          $ref: '#/components/schemas/PurposeOfAccount'
        purposeOfAccountOtherDescription:
          type: string
          maxLength: 500
          description: Free-form description of the customer's intended purpose for the Grid account. Required when `purposeOfAccount` is `OTHER`; otherwise omitted.
          example: Household budgeting between spouses
        expectedMonthlyTransactionCount:
          $ref: '#/components/schemas/ExpectedMonthlyTransactionCount'
        expectedMonthlyTransactionVolume:
          $ref: '#/components/schemas/ExpectedMonthlyTransactionVolume'
        annualIncomeRange:
          $ref: '#/components/schemas/AnnualIncomeRange'
        netWorthRange:
          $ref: '#/components/schemas/NetWorthRange'
        pepStatus:
          $ref: '#/components/schemas/PepStatus'
    IndividualCustomerFields:
      allOf:
        - type: object
          required:
            - customerType
          properties:
            customerType:
              type: string
              enum:
                - INDIVIDUAL
            kycStatus:
              $ref: '#/components/schemas/KycStatus'
            fullName:
              type: string
              maxLength: 250
              description: Individual's full name
              example: John Michael Doe
            birthDate:
              type: string
              format: date
              description: Date of birth in ISO 8601 format (YYYY-MM-DD)
              example: '1990-01-15'
            nationality:
              type: string
              description: Country code (ISO 3166-1 alpha-2)
              example: US
            address:
              $ref: '#/components/schemas/Address'
            idType:
              $ref: '#/components/schemas/IdentificationType'
            identifier:
              type: string
              maxLength: 100
              writeOnly: true
              description: The individual's identification number, required to onboard them as a US account holder. Only SSN (format `###-##-####`) and ITIN are currently accepted; other identification types are rejected. Write-only — never returned in customer responses.
              example: 123-45-6789
            countryOfIssuance:
              type: string
              description: 'Country that issued the identification (ISO 3166-1 alpha-2). Optional for an individual account holder: SSN and ITIN are US-issued, so this defaults to `US` and is rejected if set to anything else.'
              example: US
        - $ref: '#/components/schemas/CustomerEdd'
    IndividualCustomer:
      title: Individual Customer
      allOf:
        - $ref: '#/components/schemas/Customer'
        - $ref: '#/components/schemas/IndividualCustomerFields'
    KybStatus:
      type: string
      enum:
        - UNVERIFIED
        - PENDING
        - APPROVED
        - REJECTED
        - HOLD
      description: The current KYB status of a business customer. `HOLD` means the customer is placed on hold and may be required to update or provide more information.
      example: APPROVED
    EntityType:
      type: string
      enum:
        - SOLE_PROPRIETORSHIP
        - PARTNERSHIP
        - LLC
        - CORPORATION
        - S_CORPORATION
        - NON_PROFIT
        - PUBLICLY_LISTED_COMPANY
        - TRUST
        - PRIVATE_FOUNDATION
        - CHARITY
        - OTHER
      description: Legal entity type of the business
      example: LLC
    BusinessType:
      type: string
      description: The high-level industry category of the business
      enum:
        - AGRICULTURE_FORESTRY_FISHING_AND_HUNTING
        - MINING_QUARRYING_AND_OIL_AND_GAS_EXTRACTION
        - UTILITIES
        - CONSTRUCTION
        - MANUFACTURING
        - WHOLESALE_TRADE
        - RETAIL_TRADE
        - TRANSPORTATION_AND_WAREHOUSING
        - INFORMATION
        - FINANCE_AND_INSURANCE
        - REAL_ESTATE_AND_RENTAL_AND_LEASING
        - PROFESSIONAL_SCIENTIFIC_AND_TECHNICAL_SERVICES
        - MANAGEMENT_OF_COMPANIES_AND_ENTERPRISES
        - ADMINISTRATIVE_AND_SUPPORT_AND_WASTE_MANAGEMENT_AND_REMEDIATION_SERVICES
        - EDUCATIONAL_SERVICES
        - HEALTH_CARE_AND_SOCIAL_ASSISTANCE
        - ARTS_ENTERTAINMENT_AND_RECREATION
        - ACCOMMODATION_AND_FOOD_SERVICES
        - OTHER_SERVICES
        - PUBLIC_ADMINISTRATION
    SourceOfFundsCategory:
      type: string
      enum:
        - OPERATING_REVENUE
        - INVESTMENT_INCOME
        - LOANS
        - VENTURE_CAPITAL
        - PERSONAL_SAVINGS
        - DONATIONS
        - OTHER
      description: A structured source-of-funds category for the business
      example: OPERATING_REVENUE
    BusinessInfoUpdate:
      type: object
      description: Additional information for business entities
      properties:
        legalName:
          type: string
          maxLength: 250
          description: Legal name of the business
          example: Acme Corporation, Inc.
        doingBusinessAs:
          type: string
          maxLength: 250
          description: Trade name or DBA name of the business, if different from the legal name
          example: Acme
        country:
          type: string
          description: Country of incorporation or registration (ISO 3166-1 alpha-2)
          example: US
        registrationNumber:
          type: string
          maxLength: 100
          description: Business registration number
          example: '5523041'
        incorporatedOn:
          type: string
          format: date
          description: Date of incorporation in ISO 8601 format (YYYY-MM-DD)
          example: '2018-03-14'
        entityType:
          $ref: '#/components/schemas/EntityType'
        taxId:
          type: string
          maxLength: 100
          description: Tax identification number
          example: 47-1234567
        countriesOfOperation:
          type: array
          items:
            type: string
          description: List of countries where the business operates (ISO 3166-1 alpha-2)
          example:
            - US
        businessType:
          $ref: '#/components/schemas/BusinessType'
        purposeOfAccount:
          $ref: '#/components/schemas/PurposeOfAccount'
        sourceOfFunds:
          type: string
          maxLength: 500
          description: The primary source of funds for the business
          example: Funds derived from customer payments for software services
        expectedMonthlyTransactionCount:
          type: string
          enum:
            - COUNT_UNDER_10
            - COUNT_10_TO_100
            - COUNT_100_TO_500
            - COUNT_500_TO_1000
            - COUNT_OVER_1000
          description: Expected number of transactions per month
          example: COUNT_100_TO_500
        expectedMonthlyTransactionVolume:
          type: string
          enum:
            - VOLUME_UNDER_10K
            - VOLUME_10K_TO_100K
            - VOLUME_100K_TO_1M
            - VOLUME_1M_TO_10M
            - VOLUME_OVER_10M
          description: Expected total transaction volume per month in USD equivalent
          example: VOLUME_100K_TO_1M
        expectedRecipientJurisdictions:
          type: array
          items:
            type: string
          description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2)
          example:
            - US
        naicsCode:
          type: string
          pattern: ^\d{2,6}$
          description: NAICS code describing the nature of the business (2-6 digits)
          example: '541511'
        sourceOfFundsCategories:
          type: array
          items:
            $ref: '#/components/schemas/SourceOfFundsCategory'
          description: Structured source-of-funds categories for the business
          example:
            - OPERATING_REVENUE
        sourceOfFundsOtherDescription:
          type: string
          minLength: 1
          maxLength: 500
          description: Description of the source of funds when OTHER is selected
          example: Proceeds from a legal settlement
        purposeOfAccountOtherDescription:
          type: string
          minLength: 1
          maxLength: 500
          description: Description of the account purpose when OTHER is selected
          example: Escrow for equipment leases
        expectedCounterpartyCountries:
          type: array
          items:
            type: string
          description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2)
          example:
            - US
        primaryContactFirstName:
          type: string
          maxLength: 250
          description: First name of the business's primary contact — a registered director or authorised representative of the business. Required in regions where a named individual is verified against the business during onboarding (e.g. the EU). The customer's `email` and `phoneNumber` are this person's contact details.
          example: Jane
        primaryContactLastName:
          type: string
          maxLength: 250
          description: Last name of the business's primary contact.
          example: Smith
    BusinessCustomerFields:
      type: object
      required:
        - customerType
      properties:
        customerType:
          type: string
          enum:
            - BUSINESS
        kybStatus:
          $ref: '#/components/schemas/KybStatus'
        address:
          $ref: '#/components/schemas/Address'
        businessInfo:
          $ref: '#/components/schemas/BusinessInfoUpdate'
    BusinessInfoResponse:
      type: object
      description: |
        Business information returned on a customer. `taxId` and `incorporatedOn` are
        required on creation only when the customer's currency uses direct
        customer-owned accounts, so both are optional in responses.
      required:
        - legalName
      properties:
        legalName:
          type: string
          description: Legal name of the business
          example: Acme Corporation, Inc.
        doingBusinessAs:
          type: string
          description: Trade name or DBA name of the business, if different from the legal name
          example: Acme
        country:
          type: string
          description: Country of incorporation or registration (ISO 3166-1 alpha-2)
          example: US
        registrationNumber:
          type: string
          description: Business registration number
          example: '5523041'
        incorporatedOn:
          type: string
          format: date
          description: Date of incorporation in ISO 8601 format (YYYY-MM-DD)
          example: '2018-03-14'
        entityType:
          $ref: '#/components/schemas/EntityType'
        taxId:
          type: string
          description: Tax identification number
          example: 47-1234567
        countriesOfOperation:
          type: array
          items:
            type: string
          description: List of countries where the business operates (ISO 3166-1 alpha-2)
          example:
            - US
        businessType:
          $ref: '#/components/schemas/BusinessType'
        purposeOfAccount:
          $ref: '#/components/schemas/PurposeOfAccount'
        sourceOfFunds:
          type: string
          description: The primary source of funds for the business
          example: Funds derived from customer payments for software services
        expectedMonthlyTransactionCount:
          type: string
          enum:
            - COUNT_UNDER_10
            - COUNT_10_TO_100
            - COUNT_100_TO_500
            - COUNT_500_TO_1000
            - COUNT_OVER_1000
          description: Expected number of transactions per month
          example: COUNT_100_TO_500
        expectedMonthlyTransactionVolume:
          type: string
          enum:
            - VOLUME_UNDER_10K
            - VOLUME_10K_TO_100K
            - VOLUME_100K_TO_1M
            - VOLUME_1M_TO_10M
            - VOLUME_OVER_10M
          description: Expected total transaction volume per month in USD equivalent
          example: VOLUME_100K_TO_1M
        expectedRecipientJurisdictions:
          type: array
          items:
            type: string
          description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2)
          example:
            - US
        naicsCode:
          type: string
          pattern: ^\d{2,6}$
          description: NAICS code describing the nature of the business (2-6 digits)
          example: '541511'
        sourceOfFundsCategories:
          type: array
          items:
            $ref: '#/components/schemas/SourceOfFundsCategory'
          description: Structured source-of-funds categories for the business
          example:
            - OPERATING_REVENUE
        sourceOfFundsOtherDescription:
          type: string
          minLength: 1
          maxLength: 500
          description: Description of the source of funds when OTHER is selected
          example: Proceeds from a legal settlement
        purposeOfAccountOtherDescription:
          type: string
          minLength: 1
          maxLength: 500
          description: Description of the account purpose when OTHER is selected
          example: Escrow for equipment leases
        expectedCounterpartyCountries:
          type: array
          items:
            type: string
          description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2)
          example:
            - US
        primaryContactFirstName:
          type: string
          maxLength: 250
          description: First name of the business's primary contact — a registered director or authorised representative of the business. Required in regions where a named individual is verified against the business during onboarding (e.g. the EU). The customer's `email` and `phoneNumber` are this person's contact details.
          example: Jane
        primaryContactLastName:
          type: string
          maxLength: 250
          description: Last name of the business's primary contact.
          example: Smith
    BeneficialOwnerRole:
      type: string
      enum:
        - UBO
        - DIRECTOR
        - COMPANY_OFFICER
        - CONTROL_PERSON
        - TRUSTEE
        - GENERAL_PARTNER
      description: Role of the beneficial owner within the business
      example: UBO
    BeneficialOwnerPersonalInfo:
      type: object
      required:
        - firstName
        - lastName
        - birthDate
        - nationality
        - address
        - idType
        - identifier
      properties:
        firstName:
          type: string
          maxLength: 250
          description: First name of the individual
          example: Jane
        middleName:
          type: string
          maxLength: 250
          description: Middle name of the individual
          example: Marie
        lastName:
          type: string
          maxLength: 250
          description: Last name of the individual
          example: Smith
        birthDate:
          type: string
          format: date
          description: Date of birth in ISO 8601 format (YYYY-MM-DD)
          example: '1978-06-15'
        nationality:
          type: string
          description: Country of nationality (ISO 3166-1 alpha-2)
          example: US
        email:
          type: string
          format: email
          maxLength: 255
          description: Email address of the individual
          example: jane.smith@acmecorp.com
        phoneNumber:
          type: string
          description: Phone number in E.164 format
          example: '+14155550192'
          pattern: ^\+[1-9]\d{1,14}$
        address:
          $ref: '#/components/schemas/Address'
        idType:
          $ref: '#/components/schemas/IdentificationType'
        identifier:
          type: string
          maxLength: 100
          description: The identification number or value
          example: 123-45-6789
        countryOfIssuance:
          type: string
          description: Country that issued the identification (ISO 3166-1 alpha-2)
          example: US
    BeneficialOwner:
      type: object
      required:
        - id
        - customerId
        - roles
        - ownershipPercentage
        - personalInfo
        - kycStatus
        - createdAt
      properties:
        id:
          type: string
          description: Unique identifier for this beneficial owner
          example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001
        customerId:
          type: string
          description: The ID of the business customer this beneficial owner is associated with
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        roles:
          type: array
          items:
            $ref: '#/components/schemas/BeneficialOwnerRole'
          description: Roles of this person within the business
          example:
            - UBO
            - DIRECTOR
        ownershipPercentage:
          type: integer
          description: Percentage of ownership in the business (0-100)
          minimum: 0
          maximum: 100
          example: 51
        personalInfo:
          $ref: '#/components/schemas/BeneficialOwnerPersonalInfo'
        kycStatus:
          $ref: '#/components/schemas/KycStatus'
        createdAt:
          type: string
          format: date-time
          description: When this beneficial owner was created
          example: '2025-10-03T12:00:00Z'
        updatedAt:
          type: string
          format: date-time
          description: When this beneficial owner was last updated
          example: '2025-10-03T12:00:00Z'
    BusinessCustomer:
      title: Business Customer
      allOf:
        - $ref: '#/components/schemas/Customer'
        - $ref: '#/components/schemas/BusinessCustomerFields'
        - type: object
          properties:
            businessInfo:
              $ref: '#/components/schemas/BusinessInfoResponse'
            beneficialOwners:
              type: array
              items:
                $ref: '#/components/schemas/BeneficialOwner'
    CustomerOneOf:
      oneOf:
        - $ref: '#/components/schemas/IndividualCustomer'
        - $ref: '#/components/schemas/BusinessCustomer'
      discriminator:
        propertyName: customerType
        mapping:
          INDIVIDUAL: '#/components/schemas/IndividualCustomer'
          BUSINESS: '#/components/schemas/BusinessCustomer'
    CustomerListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of customers matching the filter criteria
          items:
            $ref: '#/components/schemas/CustomerOneOf'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of customers matching the criteria (excluding pagination)
    Error405:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | METHOD_NOT_ALLOWED | The HTTP method is not supported for this endpoint |
          enum:
            - METHOD_NOT_ALLOWED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    CustomerCreateRequest:
      type: object
      required:
        - customerType
      properties:
        platformCustomerId:
          type: string
          description: Platform-specific customer identifier. If not provided, one will be generated by the system.
          example: 9f84e0c2a72c4fa
        customerType:
          $ref: '#/components/schemas/CustomerType'
        region:
          type: string
          description: Country code (ISO 3166-1 alpha-2) for the customer's region. Optional — send it only when the same currency is offered in more than one of your regions, to pick the one that applies to this customer.
          example: US
        currencies:
          type: array
          items:
            type: string
          description: Currency codes the customer will use — ISO 4217 for fiat, tickers for crypto (e.g. "USDC"). Optional — send them if your platform supports more than one currency, so we know which ones this customer needs. Some combinations require separate customers.
          example:
            - USD
            - USDC
        email:
          type: string
          format: email
          maxLength: 255
          description: Email address for the customer. **Required in regions that verify the email address before identity verification** (e.g. the EU); optional otherwise.
          example: john.doe@example.com
        phoneNumber:
          type: string
          pattern: ^\+[1-9]\d{1,14}$
          description: Phone number for the customer in strict E.164 format. **Required in regions that verify the phone number before identity verification** (e.g. the EU); optional otherwise.
          example: '+14155551234'
        umaAddress:
          type: string
          description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer.
          example: $john.doe@uma.domain.com
        endUserTermsConsent:
          deprecated: true
          allOf:
            - $ref: '#/components/schemas/EndUserTermsConsent'
          description: Deprecated; send `agreementConsents` instead. Supplying this records acceptance of the Lightspark End User Terms, equivalent to a single `agreementConsents` entry of type `LIGHTSPARK_END_USER_TERMS`. Supplying both fields in one request is rejected.
        agreementConsents:
          type: array
          description: Evidence that the customer accepted Grid agreements, at most one entry per type. Unregulated platforms must record acceptance of the Lightspark End User Terms before initiating customer-scoped transactions; those transactions fail until that consent is recorded. Consents can be supplied during customer creation or in a later customer update.
          items:
            $ref: '#/components/schemas/AgreementConsentRequest'
    IndividualCustomerCreateRequest:
      title: Individual Customer Create Request
      allOf:
        - $ref: '#/components/schemas/CustomerCreateRequest'
        - $ref: '#/components/schemas/IndividualCustomerFields'
    BusinessInfo:
      type: object
      description: Additional information required for business entities
      required:
        - legalName
        - country
      properties:
        legalName:
          type: string
          maxLength: 250
          description: Legal name of the business
          example: Acme Corporation, Inc.
        doingBusinessAs:
          type: string
          maxLength: 250
          description: Trade name or DBA name of the business, if different from the legal name
          example: Acme
        country:
          type: string
          description: Country of incorporation or registration (ISO 3166-1 alpha-2)
          example: US
        registrationNumber:
          type: string
          maxLength: 100
          description: Business registration number
          example: '5523041'
        incorporatedOn:
          type: string
          format: date
          description: Date of incorporation in ISO 8601 format (YYYY-MM-DD). Required when the customer's currency uses direct customer-owned accounts. Can be omitted when the platform funds customers from an omnibus FBO account.
          example: '2018-03-14'
        entityType:
          $ref: '#/components/schemas/EntityType'
        taxId:
          type: string
          maxLength: 100
          description: Tax identification number. Required when the customer's currency uses direct customer-owned accounts. Can be omitted when the platform funds customers from an omnibus FBO account.
          example: 47-1234567
        countriesOfOperation:
          type: array
          items:
            type: string
          description: List of countries where the business operates (ISO 3166-1 alpha-2)
          example:
            - US
        businessType:
          $ref: '#/components/schemas/BusinessType'
        purposeOfAccount:
          $ref: '#/components/schemas/PurposeOfAccount'
        sourceOfFunds:
          type: string
          maxLength: 500
          description: The primary source of funds for the business
          example: Funds derived from customer payments for software services
        expectedMonthlyTransactionCount:
          type: string
          enum:
            - COUNT_UNDER_10
            - COUNT_10_TO_100
            - COUNT_100_TO_500
            - COUNT_500_TO_1000
            - COUNT_OVER_1000
          description: Expected number of transactions per month
          example: COUNT_100_TO_500
        expectedMonthlyTransactionVolume:
          type: string
          enum:
            - VOLUME_UNDER_10K
            - VOLUME_10K_TO_100K
            - VOLUME_100K_TO_1M
            - VOLUME_1M_TO_10M
            - VOLUME_OVER_10M
          description: Expected total transaction volume per month in USD equivalent
          example: VOLUME_100K_TO_1M
        expectedRecipientJurisdictions:
          type: array
          items:
            type: string
          description: List of countries where the business expects to send payments (ISO 3166-1 alpha-2)
          example:
            - US
        naicsCode:
          type: string
          pattern: ^\d{2,6}$
          description: NAICS code describing the nature of the business (2-6 digits)
          example: '541511'
        sourceOfFundsCategories:
          type: array
          items:
            $ref: '#/components/schemas/SourceOfFundsCategory'
          description: Structured source-of-funds categories for the business
          example:
            - OPERATING_REVENUE
        sourceOfFundsOtherDescription:
          type: string
          minLength: 1
          maxLength: 500
          description: Description of the source of funds when OTHER is selected
          example: Proceeds from a legal settlement
        purposeOfAccountOtherDescription:
          type: string
          minLength: 1
          maxLength: 500
          description: Description of the account purpose when OTHER is selected
          example: Escrow for equipment leases
        expectedCounterpartyCountries:
          type: array
          items:
            type: string
          description: List of countries of the business's expected transaction counterparties (ISO 3166-1 alpha-2)
          example:
            - US
        primaryContactFirstName:
          type: string
          maxLength: 250
          description: First name of the business's primary contact — a registered director or authorised representative of the business. Required in regions where a named individual is verified against the business during onboarding (e.g. the EU). The customer's `email` and `phoneNumber` are this person's contact details.
          example: Jane
        primaryContactLastName:
          type: string
          maxLength: 250
          description: Last name of the business's primary contact.
          example: Smith
    BusinessCustomerCreateRequest:
      title: Business Customer Create Request
      allOf:
        - $ref: '#/components/schemas/CustomerCreateRequest'
        - $ref: '#/components/schemas/BusinessCustomerFields'
        - type: object
          required:
            - businessInfo
          properties:
            businessInfo:
              $ref: '#/components/schemas/BusinessInfo'
    CustomerCreateRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/IndividualCustomerCreateRequest'
        - $ref: '#/components/schemas/BusinessCustomerCreateRequest'
      discriminator:
        propertyName: customerType
        mapping:
          INDIVIDUAL: '#/components/schemas/IndividualCustomerCreateRequest'
          BUSINESS: '#/components/schemas/BusinessCustomerCreateRequest'
    Error409:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL | Transaction is not pending platform approval |
            | TRANSACTION_NOT_CANCELLABLE | Transaction has already settled or is otherwise past the point where it can be cancelled |
            | UMA_ADDRESS_EXISTS | UMA address already exists |
            | EMAIL_OTP_EMAIL_ALREADY_EXISTS | Email address is already associated with an EMAIL_OTP credential |
            | EMAIL_OTP_CREDENTIAL_SET_CHANGED | Tied EMAIL_OTP credential set changed after the signed-retry challenge was issued |
            | PASSKEY_ALREADY_ENROLLED | The customer already has an enrolled passkey factor; only one passkey per customer is supported. Delete the existing one before enrolling another |
            | SCA_SESSION_REQUIRED | The customer's Strong Customer Authentication login session is missing or expired. Re-authenticate the customer, then retry the request. Distinct from a `401`, which means the platform's own API credentials were rejected |
            | BENEFICIARY_TRUSTED | The external account is currently a trusted beneficiary, so it cannot be deleted. Untrust it first via `POST /customers/external-accounts/{externalAccountId}/untrust` (and its `/confirm`), then delete |
            | BANK_ACCOUNT_VALIDATION_PENDING | The US bank account on this request is still being validated. Grid verifies a newly added ACH account by sending a micro-entry and waiting out the return window, which takes a few banking days. The account is not rejected; retry once validation completes. A permanently failed account returns `400 INVALID_BANK_ACCOUNT` instead |
            | INVALID_STATE_TRANSITION | The requested card `status` transition is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE \| FROZEN → CLOSED` |
            | CARD_ALREADY_CLOSED | `status: CLOSED` was requested for a card that is already `CLOSED` |
            | CARD_NOT_MUTABLE | The card is `CLOSED`, so it can no longer be mutated |
            | PIN_CHANGE_REQUIRED | The card's PIN unblock allowance is consumed for seven days. Change the PIN to recover sooner. `details.pinUnblockAvailableAt` gives the next allowed unblock time |
            | CARD_LIMIT_REACHED | The platform has reached the maximum number of live cards it may hold, or the cardholder already holds a card and the platform is limited to one per cardholder. Closing a card frees its slot; contact Lightspark to raise the limit |
            | STABLECOIN_GRID_ENABLEMENT_NOT_REQUESTABLE | Grid enablement can only be requested while the stablecoin is `NOT_ENABLED` (a repeat request while already `PENDING_APPROVAL` succeeds). `ENABLING`, `ENABLED` and `DISABLED` are driven by Lightspark and cannot be requested |
            | NOT_YET_AVAILABLE | The request is valid, but its result is not available yet because the data it depends on could still change. Retry later. `reason` says what is outstanding. For balance changes, card settlement for a window boundary has not closed |
            | CONFLICT | Generic resource-state conflict. Returned, for example, when `platformCustomerId` on a customer create call collides with an existing active customer on the same platform |
            | DOCUMENT_ALREADY_EXISTS | A document of this type already exists for the holder; replace it with PUT |
            | DUPLICATE_EXTERNAL_ACCOUNT | An equivalent external account already exists. When the conflicting account belongs to your platform, `details.externalAccountId` and `details.platformAccountId` identify it; otherwise `details` is omitted |
            | DUPLICATE_PAY_REQUEST | A pay request with this idempotency key already exists |
            | OPERATION_IN_PROGRESS | A conflicting Embedded Wallet operation is still in progress |
            | SMS_OTP_CREDENTIAL_SET_CHANGED | The SMS_OTP credential set changed while the request was in flight |
            | SMS_OTP_PHONE_NUMBER_ALREADY_EXISTS | The phone number is already associated with an SMS_OTP credential |
            | STABLECOIN_SYMBOL_ALREADY_EXISTS | A stablecoin with this symbol is already registered |
            | STABLECOIN_TOKEN_IDENTIFIER_ALREADY_EXISTS | A stablecoin with this token identifier is already registered |
            | WALLET_NOT_PROVISIONED | The embedded wallet has not been provisioned |
            | PAYMENT_DOCUMENT_ALREADY_USED | The payment document is already attached to a quote. A document can be used on one quote only, so upload the file again to use it on another quote |
          enum:
            - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL
            - TRANSACTION_NOT_CANCELLABLE
            - UMA_ADDRESS_EXISTS
            - EMAIL_OTP_EMAIL_ALREADY_EXISTS
            - EMAIL_OTP_CREDENTIAL_SET_CHANGED
            - PASSKEY_ALREADY_ENROLLED
            - SCA_SESSION_REQUIRED
            - BENEFICIARY_TRUSTED
            - BANK_ACCOUNT_VALIDATION_PENDING
            - INVALID_STATE_TRANSITION
            - CARD_ALREADY_CLOSED
            - CARD_NOT_MUTABLE
            - PIN_CHANGE_REQUIRED
            - CARD_LIMIT_REACHED
            - STABLECOIN_GRID_ENABLEMENT_NOT_REQUESTABLE
            - NOT_YET_AVAILABLE
            - CONFLICT
            - DOCUMENT_ALREADY_EXISTS
            - DUPLICATE_EXTERNAL_ACCOUNT
            - DUPLICATE_PAY_REQUEST
            - OPERATION_IN_PROGRESS
            - SMS_OTP_CREDENTIAL_SET_CHANGED
            - SMS_OTP_PHONE_NUMBER_ALREADY_EXISTS
            - STABLECOIN_SYMBOL_ALREADY_EXISTS
            - STABLECOIN_TOKEN_IDENTIFIER_ALREADY_EXISTS
            - WALLET_NOT_PROVISIONED
            - PAYMENT_DOCUMENT_ALREADY_USED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error404:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | TRANSACTION_NOT_FOUND | Transaction not found |
            | INVITATION_NOT_FOUND | Invitation not found |
            | USER_NOT_FOUND | Customer not found |
            | QUOTE_NOT_FOUND | Quote not found |
            | LOOKUP_REQUEST_NOT_FOUND | Lookup request not found |
            | TOKEN_NOT_FOUND | Token not found |
            | BULK_UPLOAD_JOB_NOT_FOUND | Bulk upload job not found |
            | REFERENCE_NOT_FOUND | Reference not found |
            | UMA_NOT_FOUND | The UMA address is well-formed but no receiver exists at the counterparty VASP |
            | STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND | Stablecoin provider account link not found |
            | ACCOUNT_NOT_FOUND | Account not found |
            | AUTH_METHOD_NOT_FOUND | Authentication credential not found |
            | CUSTOMER_NOT_FOUND | Customer not found |
            | DOCUMENT_HOLDER_NOT_FOUND | Document holder not found |
            | NOT_FOUND | The requested resource was not found |
            | PAYMENT_URL_NOT_FOUND | Payment URL not found |
            | PLATFORM_NOT_FOUND | Platform not found |
            | REQUEST_NOT_FOUND | Pending request not found |
            | SESSION_NOT_FOUND | Session not found |
            | STABLECOIN_EXTERNAL_ACCOUNT_NOT_FOUND | Stablecoin external account not found |
            | STABLECOIN_NOT_FOUND | Stablecoin not found |
            | STABLECOIN_OPERATION_NOT_FOUND | Stablecoin operation not found |
            | VERIFICATION_NOT_FOUND | Verification not found |
            | PAYMENT_DOCUMENT_NOT_FOUND | Payment document not found |
          enum:
            - TRANSACTION_NOT_FOUND
            - INVITATION_NOT_FOUND
            - USER_NOT_FOUND
            - QUOTE_NOT_FOUND
            - LOOKUP_REQUEST_NOT_FOUND
            - TOKEN_NOT_FOUND
            - BULK_UPLOAD_JOB_NOT_FOUND
            - REFERENCE_NOT_FOUND
            - UMA_NOT_FOUND
            - STABLECOIN_PROVIDER_ACCOUNT_NOT_FOUND
            - ACCOUNT_NOT_FOUND
            - AUTH_METHOD_NOT_FOUND
            - CUSTOMER_NOT_FOUND
            - DOCUMENT_HOLDER_NOT_FOUND
            - NOT_FOUND
            - PAYMENT_URL_NOT_FOUND
            - PLATFORM_NOT_FOUND
            - REQUEST_NOT_FOUND
            - SESSION_NOT_FOUND
            - STABLECOIN_EXTERNAL_ACCOUNT_NOT_FOUND
            - STABLECOIN_NOT_FOUND
            - STABLECOIN_OPERATION_NOT_FOUND
            - VERIFICATION_NOT_FOUND
            - PAYMENT_DOCUMENT_NOT_FOUND
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error410:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | CUSTOMER_DELETED | Customer has been permanently deleted |
            | PAYMENT_DOCUMENT_EXPIRED | The payment document passed its `expiresAt` without being attached to a quote. Upload the file again |
          enum:
            - CUSTOMER_DELETED
            - PAYMENT_DOCUMENT_EXPIRED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    CustomerUpdateRequest:
      title: Customer Update Request
      description: Request body for `PATCH /customers/{customerId}`. When `email` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer email and every tied `EMAIL_OTP` credential through the endpoint's signed-retry flow. When `phoneNumber` changes for a customer with tied Embedded Wallet internal accounts, Grid updates the customer phone number and every tied `SMS_OTP` credential through the same signed-retry flow. Update `email` and `phoneNumber` in separate PATCH calls.
      type: object
      required:
        - customerType
      properties:
        customerType:
          $ref: '#/components/schemas/CustomerType'
        currencies:
          type: array
          items:
            type: string
          description: Updated list of currency codes the customer will use (ISO 4217 for fiat, e.g. "USD", "EUR"; tickers for crypto, e.g. "BTC", "USDC"). Replaces the existing list. Some currency combinations may require separate customers — if so, the request will be rejected with details.
          example:
            - USD
            - EUR
            - USDC
        email:
          type: string
          format: email
          maxLength: 255
          description: Email address for the customer. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `EMAIL_OTP` credential across all tied Embedded Wallets.
          example: john.doe@example.com
        phoneNumber:
          type: string
          pattern: ^\+[1-9]\d{1,14}$
          description: Phone number for the customer in strict E.164 format. For customers with tied Embedded Wallet internal accounts, changing this value also updates every tied `SMS_OTP` credential across all tied Embedded Wallets. Send phone number and email updates as separate PATCH calls.
          example: '+14155551234'
        umaAddress:
          type: string
          description: Optional UMA address identifier. If provided, the customer's UMA address will be updated. This is an optional identifier to route payments to the customer.
          example: $john.doe@uma.domain.com
        endUserTermsConsent:
          deprecated: true
          allOf:
            - $ref: '#/components/schemas/EndUserTermsConsent'
          description: Deprecated; send `agreementConsents` instead. Supplying this records acceptance of the Lightspark End User Terms, equivalent to a single `agreementConsents` entry of type `LIGHTSPARK_END_USER_TERMS`. Supplying both fields in one request is rejected.
        agreementConsents:
          type: array
          description: Evidence that the customer accepted Grid agreements, at most one entry per type. Supplying this records additional acceptances; it never withdraws or replaces evidence already recorded for other types. Omitting the field leaves recorded consents unchanged.
          items:
            $ref: '#/components/schemas/AgreementConsentRequest'
    IndividualCustomerUpdateRequest:
      title: Individual Customer Update Request
      allOf:
        - $ref: '#/components/schemas/CustomerUpdateRequest'
        - $ref: '#/components/schemas/IndividualCustomerFields'
    BusinessCustomerUpdateRequest:
      title: Business Customer Update Request
      allOf:
        - $ref: '#/components/schemas/CustomerUpdateRequest'
        - $ref: '#/components/schemas/BusinessCustomerFields'
    CustomerUpdateRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/IndividualCustomerUpdateRequest'
        - $ref: '#/components/schemas/BusinessCustomerUpdateRequest'
      discriminator:
        propertyName: customerType
        mapping:
          INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest'
          BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest'
    WalletOperationProcessing:
      title: Wallet Operation Processing
      description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.'
      type: object
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - PROCESSING
          description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available.
          example: PROCESSING
        message:
          type: string
          description: Human-readable explanation that the operation is still being processed and the same request may be retried.
          example: This login is still being processed. Retry the same request in a moment.
      example:
        status: PROCESSING
        message: This login is still being processed. Retry the same request in a moment.
    CustomerUpdateResponse:
      title: Customer Update Response
      description: 'Response body for a `200` from `PATCH /customers/{customerId}`: the updated customer, or a `WalletOperationProcessing` body while the update is still in progress.'
      anyOf:
        - $ref: '#/components/schemas/CustomerOneOf'
        - $ref: '#/components/schemas/WalletOperationProcessing'
    SignedRequestChallenge:
      title: Signed Request Challenge
      type: object
      required:
        - payloadToSign
        - requestId
        - expiresAt
      description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, customer email updates, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example.
      properties:
        payloadToSign:
          type: string
          description: 'Payload for the retry authorization stamp. Build an API-key stamp over the exact UTF-8 bytes of this string, without parsing, re-serializing, or trimming it. Use the keypair required by the endpoint: the client''s TEK keypair for OTP verification, or an active session API keypair for other signed wallet actions. Send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request.'
          example: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_EXAMPLE"}'
        requestId:
          type: string
          description: Grid-issued `Request:<uuid>` identifier for this pending request. Echo this value exactly in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge.
          example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
        expiresAt:
          type: string
          format: date-time
          description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time.
          example: '2026-04-08T15:35:00Z'
    Error424:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | PAYREQ_REQUEST_FAILED | Payment request failed |
            | COUNTERPARTY_PUBKEY_FETCH_ERROR | Error fetching counterparty public key |
            | NO_COMPATIBLE_UMA_VERSION | No compatible UMA version |
            | LNURLP_REQUEST_FAILED | LNURLP request failed |
            | EMAIL_OTP_CREDENTIAL_SYNC_FAILED | Failed to update one or more tied EMAIL_OTP credentials |
            | SMS_OTP_CREDENTIAL_SYNC_FAILED | One or more tied SMS_OTP credentials could not be updated |
            | PAYMENT_DOCUMENT_ATTACHMENT_FAILED | Grid could not attach a supporting document to the payment, so it created no quote. Retry the same request with the same `Idempotency-Key` |
          enum:
            - PAYREQ_REQUEST_FAILED
            - COUNTERPARTY_PUBKEY_FETCH_ERROR
            - NO_COMPATIBLE_UMA_VERSION
            - LNURLP_REQUEST_FAILED
            - EMAIL_OTP_CREDENTIAL_SYNC_FAILED
            - SMS_OTP_CREDENTIAL_SYNC_FAILED
            - PAYMENT_DOCUMENT_ATTACHMENT_FAILED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error429:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | RATE_LIMITED | Too many requests in a short window; retry after the interval indicated by the `Retry-After` response header |
            | DAILY_VOLUME_LIMIT_EXCEEDED | The requested amount would exceed the configured daily volume limit for this trade corridor or withdrawal currency |
          enum:
            - RATE_LIMITED
            - DAILY_VOLUME_LIMIT_EXCEEDED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    AgreementDocument:
      type: object
      required:
        - type
        - version
        - url
      properties:
        type:
          $ref: '#/components/schemas/AgreementType'
        version:
          type: string
          description: Current version identifier of this agreement. Supply it as `termsVersion` when recording the customer's acceptance. Versions are independent per type.
          example: '2025-10-13'
        url:
          type: string
          format: uri
          description: URL where Grid hosts this version of the agreement. Platform-specific agreements resolve to a URL for the authenticated platform.
          example: https://www.lightspark.com/legal/grid/enduserterms
    AgreementDocumentListResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          description: Every agreement Grid supports, one entry per type. The list is a catalog of available agreements, not a list of the agreements a given customer is required to accept.
          items:
            $ref: '#/components/schemas/AgreementDocument'
    EndUserTerms:
      type: object
      deprecated: true
      required:
        - version
        - url
      description: Deprecated; read the agreement documents list instead, which reports every agreement Grid supports rather than the End User Terms alone.
      properties:
        version:
          type: string
          description: Current version identifier of the Grid End User Terms.
          example: V1
        url:
          type: string
          format: uri
          description: URL where Grid hosts this version of the End User Terms.
          example: https://www.lightspark.com/legal/grid/enduserterms
    KycLinkCreateRequest:
      type: object
      description: Request body for generating a hosted KYC link for an existing customer.
      properties:
        redirectUri:
          type: string
          format: uri
          description: URI the customer is redirected to after completing the hosted KYC flow. Must start with `https://` (or `http://` for local development). Embedded in the returned `kycUrl`.
          example: https://app.example.com/onboarding/completed
    KycProvider:
      type: string
      description: The KYC provider that will perform identity verification for the customer. Grid selects the provider based on the customer's region and platform configuration; the value is informational for platforms that want to integrate directly with the provider's SDK.
      enum:
        - SUMSUB
      example: SUMSUB
    KycLinkResponse:
      type: object
      description: A hosted KYC link that the customer can complete to verify their identity.
      required:
        - kycUrl
        - expiresAt
        - provider
      properties:
        kycUrl:
          type: string
          description: Hosted URL the customer should be sent to in order to complete verification. The URL is single-use and expires at `expiresAt`. To generate a new link (for example, after the previous one expires or is abandoned), call this endpoint again.
          example: https://kyc.lightspark.com/onboard/abc123def456
        expiresAt:
          type: string
          format: date-time
          description: Time at which the hosted link expires and can no longer be used.
          example: '2027-01-15T14:32:00Z'
        provider:
          $ref: '#/components/schemas/KycProvider'
        token:
          type: string
          description: Provider-specific token that can be used in place of the hosted URL — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same `kycStatus` update on the customer.
          example: _act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    ContactVerificationConfirmRequest:
      type: object
      description: Request body for confirming an email or phone verification challenge.
      required:
        - code
      properties:
        code:
          type: string
          description: The one-time verification code the customer received via email or SMS. In sandbox, the code is always `123456`.
          example: '123456'
    ScaFactor:
      type: string
      enum:
        - SMS_OTP
        - TOTP
        - PASSKEY
      description: |
        A Strong Customer Authentication factor.

        | Factor | Description |
        |--------|-------------|
        | `SMS_OTP` | One-time code sent by SMS to the customer's verified phone. Requires no prior enrollment. |
        | `TOTP` | Time-based one-time code from an authenticator app. Requires enrollment. Not valid for per-transaction challenges (cannot carry dynamic linking). |
        | `PASSKEY` | WebAuthn passkey assertion. Requires enrollment. |
    ScaFactorView:
      type: object
      description: An enrolled Strong Customer Authentication factor.
      required:
        - factor
      properties:
        factor:
          $ref: '#/components/schemas/ScaFactor'
          description: The kind of enrolled factor.
        credentialId:
          type: string
          description: The per-credential id, populated only for `PASSKEY` factors (the id passed to delete a passkey). Omitted for `TOTP` and `SMS_OTP`, which have no per-credential id.
        name:
          type: string
          description: An optional human-readable label for this factor.
    ScaFactorList:
      type: object
      description: The Strong Customer Authentication factors a customer has enrolled.
      required:
        - factors
      properties:
        factors:
          type: array
          description: The customer's enrolled SCA factors.
          items:
            $ref: '#/components/schemas/ScaFactorView'
    TotpFactorEnrollRequest:
      type: object
      title: TOTP Factor Enroll Request
      description: Start enrolling a time-based one-time-password (TOTP) authenticator factor.
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - TOTP
          description: Discriminator selecting the TOTP factor. TOTP enrollment needs no other input at start.
    PasskeyFactorEnrollRequest:
      type: object
      title: Passkey Factor Enroll Request
      description: Start enrolling a WebAuthn passkey factor.
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - PASSKEY
          description: Discriminator selecting the passkey factor. Passkey enrollment needs no other input at start.
    ScaFactorEnrollRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/TotpFactorEnrollRequest'
        - $ref: '#/components/schemas/PasskeyFactorEnrollRequest'
      discriminator:
        propertyName: type
        mapping:
          TOTP: '#/components/schemas/TotpFactorEnrollRequest'
          PASSKEY: '#/components/schemas/PasskeyFactorEnrollRequest'
      description: Which SCA factor to begin enrolling, selected by `type`. `SMS_OTP` is not enrollable (it uses the customer's verified phone), so only `TOTP` and `PASSKEY` are valid here.
    TotpEnrollmentStart:
      type: object
      description: The shared secret a customer's authenticator app needs to enroll a TOTP factor. Returned by `POST /sca/factors` for a `TOTP` request; the customer scans `totpUri` (an `otpauth://` provisioning URI) and confirms with the first code their app produces.
      required:
        - type
        - secret
        - secretBase32Encoded
        - totpUri
      properties:
        type:
          type: string
          enum:
            - TOTP
          description: Discriminator identifying this as the TOTP enrollment-start payload.
        secret:
          type: string
          description: The raw TOTP shared secret.
        secretBase32Encoded:
          type: string
          description: The Base32-encoded shared secret, suitable for manual entry into an authenticator app that does not scan QR codes.
        totpUri:
          type: string
          description: The `otpauth://` provisioning URI (the QR-code payload) the customer's authenticator app scans to enroll the factor.
          example: otpauth://totp/Grid:customer@example.com?secret=ABC123&issuer=Grid
    PasskeyEnrollmentStart:
      type: object
      description: Opaque WebAuthn registration options relayed to the end user's device to enroll a passkey factor. Grid performs no crypto; pass `options` to the device's WebAuthn API to produce a credential, then submit that credential to the confirm endpoint unmodified.
      required:
        - type
        - options
        - allowedOrigins
        - relyingPartyId
      properties:
        type:
          type: string
          enum:
            - PASSKEY
          description: Discriminator identifying this as the passkey enrollment-start payload.
        options:
          type: object
          additionalProperties: true
          description: Opaque WebAuthn `PublicKeyCredentialCreationOptions`. Pass to the device's WebAuthn registration API unmodified.
        allowedOrigins:
          type: array
          description: The origins the WebAuthn registration ceremony may run against. The origin the credential is produced against must be one of these and must be echoed back on the confirm call.
          items:
            type: string
          example:
            - https://app.example.com
        relyingPartyId:
          type: string
          description: The WebAuthn relying-party id the credential is bound to.
          example: app.example.com
    ScaFactorEnrollStartOneOf:
      oneOf:
        - $ref: '#/components/schemas/TotpEnrollmentStart'
        - $ref: '#/components/schemas/PasskeyEnrollmentStart'
      discriminator:
        propertyName: type
        mapping:
          TOTP: '#/components/schemas/TotpEnrollmentStart'
          PASSKEY: '#/components/schemas/PasskeyEnrollmentStart'
      description: 'The factor-specific material needed to complete enrollment, keyed by `type`: a TOTP shared secret + provisioning URI, or the WebAuthn registration options for a passkey.'
    TotpEnrollmentConfirmRequest:
      type: object
      description: The shared secret returned by the TOTP enrollment start, plus the first code the customer's authenticator app produces, submitted to confirm and finalize the TOTP factor.
      required:
        - type
        - secret
        - code
      properties:
        type:
          type: string
          enum:
            - TOTP
          description: Discriminator selecting the TOTP confirm variant.
        secret:
          type: string
          description: The shared secret returned as `secret` by the TOTP enrollment start, threaded back to bind the confirmation to that enrollment.
        code:
          type: string
          description: The current time-based one-time code from the customer's authenticator app. In sandbox, the code is always `123456`.
          example: '123456'
    PasskeyEnrollmentConfirmRequest:
      type: object
      description: The WebAuthn credential a device produced for a passkey registration challenge, submitted to enroll the passkey factor.
      required:
        - type
        - origin
        - credential
      properties:
        type:
          type: string
          enum:
            - PASSKEY
          description: Discriminator selecting the passkey confirm variant.
        origin:
          type: string
          description: The WebAuthn origin the `credential` was produced against (one of the enrollment start's `allowedOrigins`).
          example: https://app.example.com
        credential:
          type: object
          additionalProperties: true
          description: Opaque WebAuthn credential the device produced from the enrollment start's `options`.
    ScaFactorConfirmRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/TotpEnrollmentConfirmRequest'
        - $ref: '#/components/schemas/PasskeyEnrollmentConfirmRequest'
      discriminator:
        propertyName: type
        mapping:
          TOTP: '#/components/schemas/TotpEnrollmentConfirmRequest'
          PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmRequest'
      description: 'The proof that finalizes enrollment, keyed by `type`: the TOTP shared secret + code, or the passkey `origin` + `credential`.'
    TotpEnrollmentConfirmResponse:
      type: object
      description: The one-time recovery codes issued once a TOTP factor is enrolled. These are shown to the customer only once; store them somewhere safe to recover access if the authenticator device is lost.
      required:
        - type
        - recoveryCodes
      properties:
        type:
          type: string
          enum:
            - TOTP
          description: Discriminator identifying this as the TOTP enrollment result.
        recoveryCodes:
          type: array
          description: The one-time recovery codes for this TOTP factor.
          items:
            type: string
          example:
            - ABCD-EFGH-IJKL
            - MNOP-QRST-UVWX
    PasskeyEnrollmentConfirmResponse:
      type: object
      description: The enrolled passkey factor returned after a successful confirmation.
      required:
        - type
        - factor
      properties:
        type:
          type: string
          enum:
            - PASSKEY
          description: Discriminator identifying this as the passkey enrollment result.
        factor:
          $ref: '#/components/schemas/ScaFactorView'
    ScaFactorConfirmResponseOneOf:
      oneOf:
        - $ref: '#/components/schemas/TotpEnrollmentConfirmResponse'
        - $ref: '#/components/schemas/PasskeyEnrollmentConfirmResponse'
      discriminator:
        propertyName: type
        mapping:
          TOTP: '#/components/schemas/TotpEnrollmentConfirmResponse'
          PASSKEY: '#/components/schemas/PasskeyEnrollmentConfirmResponse'
      description: The enrollment result, keyed by `type`.
    ScaLoginStartRequest:
      type: object
      description: Selects which enrolled factor to start an SCA login with. The factor must already be enrolled (or, for `SMS_OTP`, the phone verified).
      required:
        - factor
      properties:
        factor:
          $ref: '#/components/schemas/ScaFactor'
          description: The factor to authenticate with.
    ScaLoginStart:
      type: object
      description: 'The factor-specific material a customer needs to complete an SCA login. Each factor surfaces only the fields it issues: `SMS_OTP` carries `challengeId` and `expiresAt`; `TOTP` carries neither (the customer reads the code from their authenticator app); `PASSKEY` carries the opaque WebAuthn `passkeyOptions` with `allowedOrigins` and `relyingPartyId`.'
      required:
        - factor
      properties:
        factor:
          $ref: '#/components/schemas/ScaFactor'
          description: The factor this login was started for.
        challengeId:
          type:
            - string
            - 'null'
          description: The challenge handle for an `SMS_OTP` login, threaded back on the complete call. Present only for `SMS_OTP`.
        expiresAt:
          type:
            - string
            - 'null'
          format: date-time
          description: Absolute UTC timestamp after which the `SMS_OTP` code expires. Present only for `SMS_OTP`.
          example: '2025-10-03T12:05:00Z'
        passkeyOptions:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: Opaque WebAuthn assertion request options. Present only for `PASSKEY`; pass to the device's WebAuthn API to produce the assertion submitted on the complete call.
        allowedOrigins:
          type:
            - array
            - 'null'
          items:
            type: string
          description: The origins the WebAuthn ceremony may run against. Present only for `PASSKEY`.
          example:
            - https://app.example.com
        relyingPartyId:
          type:
            - string
            - 'null'
          description: The WebAuthn relying-party id. Present only for `PASSKEY`.
          example: app.example.com
    ScaLoginCompleteRequest:
      type: object
      description: Completes an SCA login by submitting the proof for the started factor. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`), plus the `factor` being completed, the `endUserIpAddress` the login is being performed from, and, for `SMS_OTP`, the `challengeId` returned by the login start. Provide exactly one of `code` or `passkeyAssertion`, with `origin` alongside a `passkeyAssertion`.
      required:
        - factor
        - endUserIpAddress
      properties:
        factor:
          $ref: '#/components/schemas/ScaFactor'
          description: The factor being completed; must match the started login.
        endUserIpAddress:
          type: string
          description: The IP address of the end user's device completing this login, recorded against the login event by the SCA provider. Supply the customer's address, not your server's — it feeds the provider's risk assessment and any transaction-risk exemption.
          example: 203.0.113.42
        challengeId:
          type:
            - string
            - 'null'
          description: The challenge handle returned by the login start, required for `SMS_OTP` and omitted for other factors.
        code:
          type:
            - string
            - 'null'
          description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`.
          example: '123456'
        passkeyAssertion:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: Opaque WebAuthn assertion produced by the device from the login start's `passkeyOptions`. Required when completing a `PASSKEY` login.
        origin:
          type:
            - string
            - 'null'
          description: The WebAuthn origin the `passkeyAssertion` was produced against (one of the login start's `allowedOrigins`). Required alongside `passkeyAssertion`; omit it for the `code` path.
          example: https://app.example.com
    ScaLoginComplete:
      type: object
      description: The status of a completed SCA login session.
      required:
        - status
      properties:
        status:
          type: string
          description: The status of the login session. A successful login reports `SUCCESS`; other values indicate the login did not complete and should be surfaced to the caller.
          example: SUCCESS
        sessionExpiresAt:
          type:
            - string
            - 'null'
          format: date-time
          description: Absolute UTC timestamp after which the customer's SCA session is no longer valid and they must complete another SCA login. Money movement in SCA-regulated currencies is refused once it passes, so prompt a re-login ahead of it rather than waiting for a `SCA_SESSION_REQUIRED` failure. Present when the login established a session.
          example: '2026-01-29T12:00:00Z'
    Error423:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | ACCOUNT_LOCKED | The customer's login is temporarily locked (or suspended) after too many failed attempts. Inspect `details.lockedUntil` for when the customer may retry, and `details.failedAttempts` for the current count. |
          enum:
            - ACCOUNT_LOCKED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Lockout detail. `lockedUntil` is the UTC timestamp the customer may retry after (absent when suspended indefinitely); `failedAttempts` is the current cumulative failed-login count; `suspended` is true when the account is locked with no automatic expiry.
          properties:
            suspended:
              type: boolean
            lockedUntil:
              type:
                - string
                - 'null'
              format: date-time
            failedAttempts:
              type: integer
          additionalProperties: true
    RecordSecurityEventRequest:
      type: object
      description: Records a client-side security event for the customer so Grid can maintain the customer's login-security state (SCA session revocation and failed-login lockout).
      required:
        - eventType
        - endUserIpAddress
      properties:
        eventType:
          type: string
          enum:
            - RESET_PASSWORD_COMPLETED
            - FAILED_LOGIN_ATTEMPT
          description: |
            The security event to record:

            | Value | Effect |
            |-------|--------|
            | `RESET_PASSWORD_COMPLETED` | Revokes every active SCA session for the customer and clears the failed-login counter. |
            | `FAILED_LOGIN_ATTEMPT` | Increments the cumulative failed-login counter and escalates the lockout at each milestone: 5 attempts → 15 minutes, 6 → 30 minutes, 7 → 1 hour, 8 → 24 hours, 9 or more → suspension. |
          example: FAILED_LOGIN_ATTEMPT
        endUserIpAddress:
          type: string
          description: The IP address of the end user's device the event was observed from, recorded against the event by the SCA provider. Supply the customer's address, not your server's — it feeds the provider's risk assessment and the adaptive-authentication signals derived from this event.
          example: 203.0.113.42
    RecordSecurityEventResponse:
      type: object
      description: The customer's login-security state after recording the event, so the integrator can surface a lockout to the end user.
      required:
        - eventType
        - suspended
        - failedAttempts
      properties:
        eventType:
          type: string
          enum:
            - RESET_PASSWORD_COMPLETED
            - FAILED_LOGIN_ATTEMPT
          description: The event that was recorded.
          example: FAILED_LOGIN_ATTEMPT
        suspended:
          type: boolean
          description: Whether the customer's login is currently suspended (locked with no automatic expiry). A suspended customer must go through a password reset (`RESET_PASSWORD_COMPLETED`) to clear the lockout.
        lockedUntil:
          type:
            - string
            - 'null'
          format: date-time
          description: When the customer may attempt to log in again, if temporarily locked. Null when not locked, or when `suspended` is true (no automatic expiry).
          example: '2025-10-03T12:15:00Z'
        failedAttempts:
          type: integer
          description: The customer's current cumulative failed-login count.
          example: 5
    TwoFactorResetStartRequest:
      type: object
      description: Selects which enrolled factor to reset via the liveness-gated recovery flow, and the IP address the end user is starting the reset from.
      required:
        - factor
        - endUserIpAddress
      properties:
        factor:
          $ref: '#/components/schemas/ScaFactor'
          description: The enrolled factor to reset.
        endUserIpAddress:
          type: string
          description: The IP address of the end user's device starting this reset, recorded against the reset by the SCA provider. Supply the customer's address, not your server's — it feeds the provider's risk assessment of the recovery.
          example: 203.0.113.42
    TwoFactorResetStart:
      type: object
      description: The reset handle plus the opaque liveness handles a caller relays to the end-user device to complete the liveness check. `resetId` threads the ceremony together (status and complete reference it). `livenessAccessToken` and `verificationLink` are omitted when they are not returned.
      required:
        - resetId
      properties:
        resetId:
          type: string
          description: Identifier for this reset; pass it to the status and complete endpoints.
        livenessAccessToken:
          type:
            - string
            - 'null'
          description: Access token for the embedded liveness/verification SDK, bound to this reset. Omitted when one is not returned.
        verificationLink:
          type:
            - string
            - 'null'
          description: Hosted identity-verification page URL for completing liveness. Omitted when one is not returned.
        expiresAt:
          type:
            - string
            - 'null'
          format: date-time
          description: Absolute UTC timestamp at the end of the reset window. Omitted when one is not returned.
          example: '2025-10-03T12:30:00Z'
    TwoFactorResetStatus:
      type: object
      description: The status of an in-progress 2FA reset, polled until it reaches a terminal value.
      required:
        - status
        - factor
        - expiresAt
      properties:
        status:
          type: string
          enum:
            - INITIATED
            - PENDING_REVIEW
            - LIVENESS_PASSED
            - COMPLETED
            - REJECTED
            - EXPIRED
          description: |-
            The reset status.
            | Value | Terminal | Meaning | |-------|----------|---------| | `INITIATED` | no | Reset started; liveness not yet submitted. Keep polling. | | `PENDING_REVIEW` | no | Liveness submitted; under review. Keep polling. | | `LIVENESS_PASSED` | no | Liveness passed; call the complete endpoint to finish the reset. | | `COMPLETED` | **yes** | Reset finished; the lost factor is cleared and re-enrollment can begin. | | `REJECTED` | **yes** | Liveness failed. Stop polling; start a new reset. | | `EXPIRED` | **yes** | The reset window closed before it completed. Stop polling; start a new reset. |
            Stop polling on any terminal value.
          example: INITIATED
        factor:
          $ref: '#/components/schemas/ScaFactor'
          description: The factor being reset.
        enrollmentStatus:
          type:
            - string
            - 'null'
          enum:
            - PENDING
            - COMPLETED
            - null
          description: The enrollment status of the replacement factor. `PENDING` until the customer finishes re-enrolling, then `COMPLETED`. Null for an `SMS_OTP` reset, where completing the reset enrolls the new number directly with no separate enrollment step.
          example: PENDING
        expiresAt:
          type: string
          format: date-time
          description: When the reset window closes. Poll no longer than this.
          example: '2025-10-03T12:15:00Z'
        completedAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When the reset completed; null until then.
          example: null
    TwoFactorResetCompleteRequest:
      type: object
      description: Completes a 2FA reset. The body is required and carries the IP address the end user is completing the reset from; `mobile` is only needed when resetting the `SMS_OTP` factor to a new phone number.
      required:
        - endUserIpAddress
      properties:
        endUserIpAddress:
          type: string
          description: The IP address of the end user's device completing this reset, recorded against the reset by the SCA provider. Supply the customer's address, not your server's — it feeds the provider's risk assessment of the recovery.
          example: 203.0.113.42
        mobile:
          type: object
          description: For an `SMS_OTP` reset, the new mobile number to enroll as the customer completes the reset. Required for an `SMS_OTP` reset; ignored for `TOTP` / `PASSKEY` resets.
          required:
            - countryCode
            - number
          properties:
            countryCode:
              type: string
              description: The country dialing code, including the leading `+`.
              example: '+1'
            number:
              type: string
              description: The national subscriber number, without the country code.
              example: '4155550123'
    ScaChallenge:
      type: object
      description: |-
        A Strong Customer Authentication challenge that must be satisfied before a money-movement operation can complete. This object is **only present when the customer is in a region where SCA is required** (the EU); for customers outside SCA-regulated regions it is omitted entirely and no action is needed.

        When present on a quote, authorize it by submitting an `ScaAuthorization` proof to `POST /quotes/{quoteId}/authorize`.

        **A single operation may require more than one authorization, in sequence.** Treat `scaChallenge` as *the challenge to satisfy now*, not "the only one". After each authorize, re-inspect the returned quote/transaction: if it is still `PENDING_AUTHORIZATION`, it carries the **next** `scaChallenge` (a new `id`) — authorize that too, and repeat until it leaves `PENDING_AUTHORIZATION`. Do not assume one authorization releases the transfer. The number of authorizations is flow-dependent and **may decrease in future**: for example, a cross-currency send today authorizes the currency conversion and the payout as two separate challenges; a future update may collapse them into one. A client written to loop on status handles any count unchanged.
      required:
        - id
        - expiresAt
        - factor
        - availableFactors
      properties:
        id:
          type: string
          description: Unique identifier for this challenge. The server resolves the active challenge from the quote or transaction being authorized, so this field need not be supplied back; it is informational (e.g. for logging or correlation).
          example: ScaChallenge:019542f5-b3e7-1d02-0000-000000000007
        expiresAt:
          type: string
          format: date-time
          description: Absolute UTC timestamp after which this challenge can no longer be authorized.
          example: '2025-10-03T12:05:00Z'
        factor:
          $ref: '#/components/schemas/ScaFactor'
          description: The factor this challenge was issued for. Defaults to `SMS_OTP`.
        availableFactors:
          type: array
          description: The factors the customer may use to satisfy this challenge.
          items:
            $ref: '#/components/schemas/ScaFactor'
          example:
            - SMS_OTP
        purpose:
          type:
            - string
            - 'null'
          description: Optional, informational label for what this particular challenge in the sequence authorizes — useful for step UX (e.g. "Authorize the currency conversion" vs "Authorize the payout"). Known values include `CURRENCY_CONVERSION`, `PAYOUT`, and `TRANSFER`, but the set is **non-exhaustive and may grow** — treat unrecognized values as a generic authorization step and do not branch program logic on it. Omitted when steps are not distinguished (e.g. a single-authorization flow).
          example: PAYOUT
        passkeyAssertionOptions:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: Opaque WebAuthn assertion request options (including the relying-party id, challenge, and allowed credentials), present only when `factor` is `PASSKEY`. Pass to the device's WebAuthn API to produce the assertion submitted back in `ScaAuthorization.passkeyAssertion`.
        passkeyAllowedOrigins:
          type:
            - array
            - 'null'
          items:
            type: string
          description: The origins the WebAuthn ceremony may run against. Populated for enrollment and login passkey challenges; the origin the assertion is produced against must be one of these and echoed back as `ScaAuthorization.origin`. Per-transaction passkey challenges omit this (they carry `passkeyAssertionOptions` only) — see `ScaAuthorization.origin` for how to source the origin in that case.
          example:
            - https://app.example.com
    BeneficiaryTrustStart:
      type: object
      description: The SCA challenge (if any) a caller authorizes to finish trusting (or untrusting) a beneficiary. The beneficiary is identified by its `externalAccountId`, so the confirm call needs no separate handle. `scaChallenge` is omitted when no challenge is issued; the caller then confirms without a `challengeId`.
      properties:
        scaChallenge:
          $ref: '#/components/schemas/ScaChallenge'
          description: The SCA challenge to satisfy on the confirm call. Omitted when no challenge is issued.
    BeneficiaryTrustConfirmRequest:
      type: object
      description: Confirms trusting or untrusting a beneficiary by submitting the SCA proof. Carries the same proof fields as an `ScaAuthorization` (`code` for `SMS_OTP` / `TOTP`, or `passkeyAssertion` + `origin` for `PASSKEY`) and, when the start issued one, the `challengeId`. The beneficiary is identified by the `externalAccountId` in the path — no separate handle is needed. Provide exactly one of `code` or `passkeyAssertion`, with `origin` alongside a `passkeyAssertion`.
      properties:
        challengeId:
          type:
            - string
            - 'null'
          description: The challenge handle from the trust start's `scaChallenge`, when one was issued. Omit when the start returned no challenge.
        code:
          type:
            - string
            - 'null'
          description: The one-time code the customer received by SMS, or read from their authenticator app. Provide for `SMS_OTP` / `TOTP`. In sandbox, the code is always `123456`.
          example: '123456'
        passkeyAssertion:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: Opaque WebAuthn assertion produced by the device from the challenge's assertion options. Required when satisfying a `PASSKEY` challenge.
        origin:
          type:
            - string
            - 'null'
          description: The WebAuthn origin the `passkeyAssertion` was produced against. Required alongside `passkeyAssertion`; omit it for the `code` path.
          example: https://app.example.com
    BeneficiaryTrustConfirm:
      type: object
      description: The result of a confirm-trust / confirm-untrust call. `trusted` is `true` after a successful trust and `false` after a successful untrust.
      required:
        - trusted
      properties:
        trusted:
          type: boolean
          description: Whether the beneficiary is now trusted. `true` after a successful trust, `false` after a successful untrust.
    InternalAccountType:
      title: Internal Account Type
      type: string
      enum:
        - INTERNAL_FIAT
        - INTERNAL_CRYPTO
        - EMBEDDED_WALLET
        - RULE_BASED
        - BULK_SETTLEMENT
      description: |-
        Classification of an internal account.

        - `INTERNAL_FIAT`: A Grid-managed fiat holding account (for example, the USD holding account used as the source for Payouts flows).
        - `INTERNAL_CRYPTO`: A Grid-managed crypto holding account denominated in a stablecoin such as USDC.
        - `EMBEDDED_WALLET`: A self-custodial Embedded Wallet provisioned for the customer. Outbound transfers require a session signature produced by the customer's device — see the Embedded Wallets guide.
        - `RULE_BASED`: An additional account number for an existing account holder, with a routing rule attached, so incoming payments can be attributed to a specific payer and swept automatically. Created with `POST /internal-accounts`.
        - `BULK_SETTLEMENT`: An account for receiving bulk payments (for example, a single settlement from a payment processor) that the platform then pays out to its customers. Only a platform can hold one, and funds in it can move only to the platform's customers.
    SweepRuleDestinationRequest:
      title: Sweep Rule Destination Request
      type: object
      description: The account a rule-based account sweeps its incoming payments to.
      required:
        - accountId
      properties:
        accountId:
          type: string
          description: Reference to the account that receives the swept funds. May be an external account or another internal account, but never a `RULE_BASED` internal account — that account's own rule would sweep the funds on again. The destination may be denominated in a different currency, in which case the sweep is converted at the prevailing rate.
          example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
        paymentRail:
          description: The payment rail to use when sweeping to an external account. Must be one of the rails supported by the destination account. If omitted, a rail is selected automatically for each sweep. Not accepted when the destination is an internal account, which settles without a payment rail.
          allOf:
            - $ref: '#/components/schemas/PaymentRail'
    PurposeOfPayment:
      type: string
      description: |-
        The purpose of the payment. This may be required when sending to certain geographies (e.g. India).

        Some destinations accept only certain purposes. A business payout to China must use one of the purposes listed in [Supporting Documents](https://docs.lightspark.com/payouts-and-b2b/payment-flow/send-payment#supporting-documents), and each needs its own supporting documents.
      enum:
        - GIFT
        - SELF
        - GOODS_OR_SERVICES
        - EDUCATION
        - HEALTH_OR_MEDICAL
        - REAL_ESTATE_PURCHASE
        - TAX_PAYMENT
        - LOAN_PAYMENT
        - UTILITY_BILL
        - DONATION
        - TRAVEL
        - FAMILY_SUPPORT
        - SALARY_PAYMENT
        - EXPORTED_GOODS_PREPAYMENT
        - EXPORTED_GOODS_POSTPAYMENT
        - SERVICE_CHARGES
        - OFFICE_EXPENSES
        - DELIVERY_FEES
        - HOTEL_ACCOMMODATION
        - COMMISSION_ON_GOODS
        - COMMISSION_ON_SERVICES
        - ACCOUNTING_SERVICES
        - EXHIBITION_SERVICES
        - OTHER
    PlatformFeeOverride:
      type: object
      description: Overrides the platform-collected fee for this transaction. When present, it replaces any configured platform-collected fees that would otherwise apply to the transaction. Currently only supported when the quote's source currency is USD; the fixed fee must be denominated in the source currency.
      required:
        - platformFixedFee
        - platformVariableFeeBps
      properties:
        platformFixedFee:
          $ref: '#/components/schemas/FixedFee'
          description: Fixed fee charged for this transaction. Must be denominated in the quote's source currency (USD today).
        platformVariableFeeBps:
          type: integer
          minimum: 0
          maximum: 10000
          description: Variable fee in basis points (1 bps = 0.01%) to apply to the transaction's source-currency amount.
          example: 30
      example:
        platformFixedFee:
          amount: 50
          currency: USD
        platformVariableFeeBps: 30
    SweepRuleRequest:
      title: Sweep Rule Request
      type: object
      description: The routing rule attached to a rule-based account. Every payment that settles into the account is swept to the rule's destination, carrying the payment metadata configured here.
      required:
        - destination
      properties:
        destination:
          allOf:
            - $ref: '#/components/schemas/SweepRuleDestinationRequest'
          description: Where funds that settle into this account are swept.
        purposeOfPayment:
          allOf:
            - $ref: '#/components/schemas/PurposeOfPayment'
          description: The purpose of payment applied to each sweep. Required by some destination geographies.
          example: SELF
        description:
          type: string
          maxLength: 255
          description: Free-form description recorded on each sweep. Not delivered to the recipient; use `remittanceInformation` for that.
          example: Rent sweep
        remittanceInformation:
          type: string
          maxLength: 1024
          description: 'Free-form information that travels with each sweep to the recipient. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information. Only printable ASCII characters are accepted, because the underlying rails carry nothing else.'
          example: Unit 4B March
        platformFeeOverride:
          allOf:
            - $ref: '#/components/schemas/PlatformFeeOverride'
          description: Fee terms applied to every sweep this rule drives. Omit it to use the platform's configured fees.
    InternalAccountCreateRequest:
      title: Internal Account Create Request
      type: object
      description: Request body for `POST /internal-accounts`. Only `RULE_BASED` accounts can be created today, and they are rejected without a `sweepRule` — the type and its rule are created together or not at all.
      required:
        - type
        - currency
      properties:
        customerId:
          type: string
          description: The customer the account is created for. Omit it to create a platform-owned account. The account holder must already have a verified account in the same currency, which is where funds land when a sweep cannot be completed.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        type:
          allOf:
            - $ref: '#/components/schemas/InternalAccountType'
          description: Must be `RULE_BASED`. `INTERNAL_FIAT`, `INTERNAL_CRYPTO`, `BULK_SETTLEMENT`, and `EMBEDDED_WALLET` accounts are provisioned when a customer is created or approved.
          example: RULE_BASED
        currency:
          type: string
          description: Currency code the account is denominated in (ISO 4217). Rule-based accounts are currently available in `USD` only.
          example: USD
        onBehalfOfCustomerId:
          type: string
          description: For regulated financial institutions only. The customer whose payments this platform-owned account receives. Only accepted when `customerId` is omitted, so the account stays yours; it records who incoming payments are for. The customer must belong to your platform. Several of your accounts can name the same customer.
          example: Customer:019542f5-b3e7-1d02-0000-000000000002
        label:
          type: string
          maxLength: 255
          description: Your own name for the account, echoed back on reads. Useful for identifying which payer the account was issued for.
          example: invoice-4417
        sweepRule:
          allOf:
            - $ref: '#/components/schemas/SweepRuleRequest'
          description: 'The routing rule for the account. Required when `type` is `RULE_BASED`, which is the only type this endpoint creates today: a rule-based account with no rule has no meaning, so the two are written together.'
    InternalAccountStatus:
      title: Internal Account Status
      type: string
      enum:
        - PENDING
        - ACTIVE
        - CLOSED
        - FROZEN
        - FAILED
      description: |-
        Status of a Grid internal account. The status determines whether the account can send or receive payments.

        - `PENDING`: The account is under review and is being provisioned. The account cannot send or receive payments until provisioning completes.
        - `ACTIVE`: The account is ready to send and receive payments.
        - `CLOSED`: The account cannot send or receive payments. A customer can initiate the closing of an internal account, after which the account transitions to this status.
        - `FROZEN`: The account cannot send or receive payments. Grid may freeze an account in response to compliance or fraud signals; payments are blocked while the account remains frozen.
        - `FAILED`: The account could not be provisioned. Grid was unable to create the underlying account, so it cannot send or receive payments and requires remediation.
      example: ACTIVE
    CurrencyAmount:
      type: object
      required:
        - amount
        - currency
      properties:
        amount:
          type: integer
          format: int64
          description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC)
          example: 12550
        currency:
          $ref: '#/components/schemas/Currency'
    PaymentAccountType:
      type: string
      enum:
        - USD_ACCOUNT
        - BRL_ACCOUNT
        - MXN_ACCOUNT
        - DKK_ACCOUNT
        - ARS_ACCOUNT
        - COP_ACCOUNT
        - EUR_ACCOUNT
        - INR_ACCOUNT
        - NGN_ACCOUNT
        - CAD_ACCOUNT
        - GBP_ACCOUNT
        - HKD_ACCOUNT
        - IDR_ACCOUNT
        - MYR_ACCOUNT
        - PHP_ACCOUNT
        - SGD_ACCOUNT
        - THB_ACCOUNT
        - VND_ACCOUNT
        - SWIFT_ACCOUNT
        - SPARK_WALLET
        - LIGHTNING
        - SOLANA_WALLET
        - TRON_WALLET
        - PLASMA_WALLET
        - ARBITRUM_WALLET
        - POLYGON_WALLET
        - BASE_WALLET
        - ETHEREUM_WALLET
        - EMBEDDED_WALLET
        - BITCOIN_L1
      description: Type of payment account or wallet
      example: USD_ACCOUNT
    BasePaymentAccountInfo:
      type: object
      required:
        - accountType
      properties:
        accountType:
          $ref: '#/components/schemas/PaymentAccountType'
    UsdAccountInfoBase:
      type: object
      required:
        - accountType
        - accountNumber
        - routingNumber
      properties:
        accountType:
          type: string
          enum:
            - USD_ACCOUNT
        accountNumber:
          type: string
          description: The account number of the bank
          minLength: 1
          maxLength: 34
        routingNumber:
          type: string
          description: The ABA routing number
          example: '021000021'
          minLength: 9
          maxLength: 9
          pattern: ^[0-9]{9}$
        bankName:
          type: string
          description: The name of the financial institution holding the account. Optional on every rail, and recommended for wires, where it identifies the beneficiary's institution on the payment message. Not checked against `GET /discoveries`.
          minLength: 1
          maxLength: 140
          example: Chase Bank
        intermediaryBankName:
          type: string
          description: The name of the intermediary financial institution, for accounts reachable only through a correspondent bank. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW.
          example: JPMorgan Chase Bank
          minLength: 1
          maxLength: 140
        intermediaryRoutingNumber:
          type: string
          description: The ABA routing number of the intermediary financial institution. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW.
          example: '021000021'
          minLength: 9
          maxLength: 9
          pattern: ^[0-9]{9}$
        fiToFiInformation:
          type: string
          description: Bank-to-bank instructions carried alongside the payment. Used on the WIRE rail; ignored on ACH, RTP and FEDNOW.
          example: /BNF/Invoice 4471
          maxLength: 210
      example:
        accountType: USD_ACCOUNT
        accountNumber: '1234567890'
        routingNumber: '021000021'
        bankName: Chase Bank
        intermediaryBankName: JPMorgan Chase Bank
        intermediaryRoutingNumber: '021000021'
        fiToFiInformation: /BNF/Invoice 4471
        bankAccountType: CHECKING
    UsdAccountInfo:
      allOf:
        - $ref: '#/components/schemas/UsdAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - ACH
                  - ACH_SAME_DAY
                  - WIRE
                  - RTP
                  - FEDNOW
            bankAccountType:
              type: string
              description: Whether the account is a checking or a savings account. Grid uses this to set the ACH transaction code, so a value that does not match the account causes the receiving bank to return a notification of change.
              enum:
                - CHECKING
                - SAVINGS
              example: CHECKING
    PaymentUsdAccountInfo:
      title: USD Bank Account
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/UsdAccountInfo'
        - type: object
          required:
            - reference
          properties:
            reference:
              type: string
              description: Unique reference code that must be included with the payment to properly credit it
              example: UMA-Q12345-REF
            bankAddress:
              type: string
              description: The postal address of the financial institution holding the account, on a single line. Optional on every rail, and recommended for wires, where some originating banks require the beneficiary institution's address before they will send.
              example: 885 Teaneck Road, Teaneck, NJ 07666
              minLength: 1
              maxLength: 255
    EurAccountInfoBase:
      type: object
      required:
        - accountType
        - iban
      properties:
        accountType:
          type: string
          enum:
            - EUR_ACCOUNT
        iban:
          type: string
          description: The IBAN of the bank account
          example: DE89370400440532013000
          minLength: 15
          maxLength: 34
          pattern: ^[A-Z]{2}[0-9]{2}[A-Za-z0-9]{11,30}$
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank. When omitted, Grid derives it from the IBAN when possible. Provide it when automatic derivation is unavailable.
          example: DEUTDEFF
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
      example:
        accountType: EUR_ACCOUNT
        iban: DE89370400440532013000
        swiftCode: DEUTDEFF
    EurAccountInfo:
      allOf:
        - $ref: '#/components/schemas/EurAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - SEPA
                  - SEPA_INSTANT
    PaymentEurAccountInfo:
      title: EUR Account
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/EurAccountInfo'
        - type: object
          required:
            - reference
          properties:
            reference:
              type: string
              description: Unique reference code that must be included with the payment to properly credit it
              example: UMA-Q12345-REF
    SwiftAccountInfoBase:
      type: object
      description: 'At least one of accountNumber or iban is always present: IBAN-only corridors (e.g. BR, GB) use iban, other corridors use accountNumber, and both appear when the bank exposes both identifiers for the same account.'
      required:
        - accountType
        - swiftCode
        - bankName
        - country
      properties:
        accountType:
          type: string
          enum:
            - SWIFT_ACCOUNT
        country:
          type: string
          description: The ISO 3166-1 alpha-2 country code of the bank account
          example: NG
          minLength: 2
          maxLength: 2
          pattern: ^[A-Z]{2}$
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: DEUTDEFF
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
        bankName:
          type: string
          description: The name of the bank
          example: Deutsche Bank
          minLength: 1
          maxLength: 255
        accountNumber:
          type: string
          description: The bank account number. Required for most corridors. Use iban instead for IBAN-only corridors (e.g. BR, GB).
          example: '1234567890'
          minLength: 1
          maxLength: 34
        iban:
          type: string
          description: The IBAN of the bank account. Required for IBAN-only corridors (e.g. BR, GB). Use accountNumber for all other corridors.
          example: GB29NWBK60161331926819
          minLength: 15
          maxLength: 34
          pattern: ^[A-Z]{2}[0-9]{2}[A-Za-z0-9]{11,30}$
      example:
        accountType: SWIFT_ACCOUNT
        country: NG
        swiftCode: DEUTDEFF
        bankName: Deutsche Bank
        accountNumber: '1234567890'
    SwiftAccountInfo:
      allOf:
        - $ref: '#/components/schemas/SwiftAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - SWIFT
    PaymentSwiftAccountInfo:
      title: SWIFT Account
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/SwiftAccountInfo'
        - type: object
          required:
            - accountHolderName
          properties:
            accountHolderName:
              type: string
              description: The name of the account holder as it must appear on the wire. Remitting banks match this against the beneficiary name field, so payers should copy it exactly.
              example: Acme Exports Pte Ltd
              minLength: 1
              maxLength: 255
            bankAddress:
              type: string
              description: The address of the bank holding the account, when known.
              example: 12 Marina Boulevard, Singapore 018982
              minLength: 1
              maxLength: 1024
            reference:
              type: string
              description: Reference code to include with the payment when present. SWIFT payments are attributed by the destination account number/IBAN, so this account type typically requires no reference.
              example: UMA-Q12345-REF
    MxnAccountInfoBase:
      type: object
      required:
        - accountType
        - clabeNumber
      properties:
        accountType:
          type: string
          enum:
            - MXN_ACCOUNT
        clabeNumber:
          type: string
          description: The CLABE number of the bank
          example: '123456789012345678'
          minLength: 18
          maxLength: 18
          pattern: ^[0-9]{18}$
      example:
        accountType: MXN_ACCOUNT
        clabeNumber: '123456789012345678'
    MxnAccountInfo:
      allOf:
        - $ref: '#/components/schemas/MxnAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - SPEI
    PaymentMxnAccountInfo:
      title: MXN Account
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/MxnAccountInfo'
        - type: object
          required:
            - reference
          properties:
            reference:
              type: string
              description: Unique reference code that must be included with the payment to properly credit it
              example: UMA-Q12345-REF
    PaymentBrlAccountInfo:
      title: BRL Account
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - type: object
          required:
            - qrCode
          properties:
            accountType:
              type: string
              enum:
                - BRL_ACCOUNT
            qrCode:
              type: string
              description: A PIX QR code payload that can be used to fund the transaction. This can be rendered as a QR code image or pasted into a PIX-compatible banking app.
              minLength: 1
    PaymentCopAccountInfo:
      title: COP Account
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - type: object
          required:
            - paymentUrl
          properties:
            accountType:
              type: string
              enum:
                - COP_ACCOUNT
            paymentUrl:
              type: string
              format: uri
              description: A payment URL where the customer can complete their COP deposit.
              example: https://payments.example.com/t/abc123
    PaymentArsAccountInfo:
      title: ARS Account
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - type: object
          required:
            - accountNumber
          properties:
            accountType:
              type: string
              enum:
                - ARS_ACCOUNT
            accountNumber:
              type: string
              description: The static CVU (Clave Virtual Uniforme) bank account number to pay to.
              minLength: 22
              maxLength: 22
              pattern: ^\d{22}$
              example: '0000003100074052501434'
    SparkWalletInfo:
      type: object
      required:
        - address
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - SPARK_WALLET
        address:
          type: string
          description: Spark wallet address
          example: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu
    PaymentSparkWalletInfo:
      title: Spark Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/SparkWalletInfo'
        - type: object
          required:
            - assetType
          properties:
            assetType:
              type: string
              description: Type of asset or configured Spark token currency code
            invoice:
              type: string
              description: Invoice for the payment
              example: sparkrt1pgss8ter0fhc4c220f3zftmpz49h8wqte8eg3m5zkrraplgc048jucgszg3ssqgjzqqekv73mmh842yj7drsjwh7t7tz5zt8wf5kghm5v4ehggszppjp5s80cg3qjdzc55g2567tn3lj705hdsr577tg8ah795mlnt6807y657qhkmgfkf9w75p4wz3l8vhua85zdn6ryj32zuj0p00pv2l5z4u47mw6h4s
    PaymentLightningInvoiceInfo:
      title: Lightning Invoice
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - type: object
          required:
            - invoice
          properties:
            accountType:
              type: string
              enum:
                - LIGHTNING
            invoice:
              type: string
              description: Invoice for the payment
              example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs
    PaymentBitcoinDepositAddressInfo:
      title: Bitcoin L1 Deposit Address
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - type: object
          required:
            - address
          properties:
            accountType:
              type: string
              enum:
                - BITCOIN_L1
            address:
              type: string
              description: On-chain Bitcoin (L1) deposit address to send funds to
              example: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
            network:
              type: string
              description: The blockchain network for the deposit address.
              enum:
                - BITCOIN
              example: BITCOIN
    SolanaWalletInfo:
      type: object
      required:
        - address
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - SOLANA_WALLET
        address:
          type: string
          description: Solana wallet address
          example: 4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg
    PaymentSolanaWalletInfo:
      title: Solana Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/SolanaWalletInfo'
        - type: object
          properties:
            assetType:
              type: string
              description: Type of asset
              enum:
                - USDC
                - USDT
    EthereumWalletInfo:
      type: object
      required:
        - address
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - ETHEREUM_WALLET
        address:
          type: string
          description: Ethereum L1 wallet address
          example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
    PaymentEthereumWalletInfo:
      title: Ethereum Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/EthereumWalletInfo'
        - type: object
          properties:
            assetType:
              type: string
              description: Type of asset
              enum:
                - USDC
                - USDT
    BaseWalletInfo:
      type: object
      required:
        - address
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - BASE_WALLET
        address:
          type: string
          description: Base eth wallet address
          example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
    PaymentBaseWalletInfo:
      title: Base Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/BaseWalletInfo'
        - type: object
          properties:
            assetType:
              type: string
              description: Type of asset
              enum:
                - USDC
    PolygonWalletInfo:
      type: object
      required:
        - address
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - POLYGON_WALLET
        address:
          type: string
          description: Polygon eth wallet address
          example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
    PaymentPolygonWalletInfo:
      title: Polygon Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/PolygonWalletInfo'
        - type: object
          properties:
            assetType:
              type: string
              description: Type of asset
              enum:
                - USDC
    ArbitrumWalletInfo:
      type: object
      required:
        - address
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - ARBITRUM_WALLET
        address:
          type: string
          description: Arbitrum wallet address
          example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
    PaymentArbitrumWalletInfo:
      title: Arbitrum Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/ArbitrumWalletInfo'
        - type: object
          properties:
            assetType:
              type: string
              description: Type of asset
              enum:
                - USDC
                - USDT
    TronWalletInfo:
      type: object
      required:
        - address
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - TRON_WALLET
        address:
          type: string
          description: Tron wallet address
          example: TNPeeaaFB7K9cmo4uQpcU32zGK8G1NYqeL
    PaymentTronWalletInfo:
      title: Tron Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/TronWalletInfo'
        - type: object
          properties:
            assetType:
              type: string
              description: Type of asset
              enum:
                - USDT
    PlasmaWalletInfo:
      type: object
      required:
        - address
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - PLASMA_WALLET
        address:
          type: string
          description: Plasma wallet address
          example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12'
    PaymentPlasmaWalletInfo:
      title: Plasma Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - $ref: '#/components/schemas/PlasmaWalletInfo'
        - type: object
          properties:
            assetType:
              type: string
              description: Type of asset
              enum:
                - USDT
    PaymentEmbeddedWalletInfo:
      title: Embedded Wallet
      allOf:
        - $ref: '#/components/schemas/BasePaymentAccountInfo'
        - type: object
          required:
            - accountType
            - payloadToSign
          properties:
            accountType:
              type: string
              enum:
                - EMBEDDED_WALLET
              description: Discriminator value identifying this as Embedded Wallet payment instructions.
            payloadToSign:
              type: string
              description: JSON-encoded transaction signing payload that must be stamped, as-is (byte-for-byte, without re-serialization), with the session private key of a verified authentication credential on the source Embedded Wallet. The resulting Grid wallet signature is passed as the `Grid-Wallet-Signature` header on `POST /quotes/{quoteId}/execute` to authorize the outbound transfer from the wallet.
              example: '{"type":"ACTIVITY_TYPE_SIGN_TRANSACTION_V2","timestampMs":"1746736509954","organizationId":"org_abc123","parameters":{"signWith":"wallet_abc123def456","unsignedTransaction":"ea69b4bf05f775209f26ff0a34a05569180f7936579d5c4af9377ae550194f72","type":"TRANSACTION_TYPE_ETHEREUM"},"generateAppProofs":true}'
    PaymentInstructions:
      type: object
      required:
        - accountOrWalletInfo
      properties:
        instructionsNotes:
          type: string
          description: Additional human-readable instructions for making the payment
          example: Please ensure the reference code is included in the payment memo/description field
        isPlatformAccount:
          type: boolean
          description: Indicates whether the account is a platform account or a customer account.
          example: true
        accountOrWalletInfo:
          description: Where to send the funds. Quotes can carry any of these types. Internal accounts return only `USD_ACCOUNT`, `EUR_ACCOUNT`, `SWIFT_ACCOUNT`, and the network wallet types; MXN, BRL, COP, ARS, Lightning, and Bitcoin L1 instructions appear only on real-time funded quotes.
          oneOf:
            - $ref: '#/components/schemas/PaymentUsdAccountInfo'
            - $ref: '#/components/schemas/PaymentEurAccountInfo'
            - $ref: '#/components/schemas/PaymentSwiftAccountInfo'
            - $ref: '#/components/schemas/PaymentMxnAccountInfo'
            - $ref: '#/components/schemas/PaymentBrlAccountInfo'
            - $ref: '#/components/schemas/PaymentCopAccountInfo'
            - $ref: '#/components/schemas/PaymentArsAccountInfo'
            - $ref: '#/components/schemas/PaymentSparkWalletInfo'
            - $ref: '#/components/schemas/PaymentLightningInvoiceInfo'
            - $ref: '#/components/schemas/PaymentBitcoinDepositAddressInfo'
            - $ref: '#/components/schemas/PaymentSolanaWalletInfo'
            - $ref: '#/components/schemas/PaymentEthereumWalletInfo'
            - $ref: '#/components/schemas/PaymentBaseWalletInfo'
            - $ref: '#/components/schemas/PaymentPolygonWalletInfo'
            - $ref: '#/components/schemas/PaymentArbitrumWalletInfo'
            - $ref: '#/components/schemas/PaymentTronWalletInfo'
            - $ref: '#/components/schemas/PaymentPlasmaWalletInfo'
            - $ref: '#/components/schemas/PaymentEmbeddedWalletInfo'
          discriminator:
            propertyName: accountType
            mapping:
              USD_ACCOUNT: '#/components/schemas/PaymentUsdAccountInfo'
              EUR_ACCOUNT: '#/components/schemas/PaymentEurAccountInfo'
              SWIFT_ACCOUNT: '#/components/schemas/PaymentSwiftAccountInfo'
              MXN_ACCOUNT: '#/components/schemas/PaymentMxnAccountInfo'
              BRL_ACCOUNT: '#/components/schemas/PaymentBrlAccountInfo'
              COP_ACCOUNT: '#/components/schemas/PaymentCopAccountInfo'
              ARS_ACCOUNT: '#/components/schemas/PaymentArsAccountInfo'
              SPARK_WALLET: '#/components/schemas/PaymentSparkWalletInfo'
              LIGHTNING: '#/components/schemas/PaymentLightningInvoiceInfo'
              BITCOIN_L1: '#/components/schemas/PaymentBitcoinDepositAddressInfo'
              SOLANA_WALLET: '#/components/schemas/PaymentSolanaWalletInfo'
              ETHEREUM_WALLET: '#/components/schemas/PaymentEthereumWalletInfo'
              BASE_WALLET: '#/components/schemas/PaymentBaseWalletInfo'
              POLYGON_WALLET: '#/components/schemas/PaymentPolygonWalletInfo'
              ARBITRUM_WALLET: '#/components/schemas/PaymentArbitrumWalletInfo'
              TRON_WALLET: '#/components/schemas/PaymentTronWalletInfo'
              PLASMA_WALLET: '#/components/schemas/PaymentPlasmaWalletInfo'
              EMBEDDED_WALLET: '#/components/schemas/PaymentEmbeddedWalletInfo'
    SweepRuleDestination:
      title: Sweep Rule Destination
      type: object
      description: Where a rule-based account's credits are swept.
      required:
        - accountId
      properties:
        accountId:
          type: string
          description: The account that receives the swept funds.
          example: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
        paymentRail:
          allOf:
            - $ref: '#/components/schemas/PaymentRail'
          description: The rail each sweep is sent over. Null when a rail is selected automatically per sweep, in which case none is resolved ahead of time.
          example: ACH
    SweepRule:
      title: Sweep Rule
      type: object
      description: The routing rule attached to a rule-based account. Returned on the account rather than as a resource of its own, because the rule has no lifecycle apart from the account.
      required:
        - destination
      properties:
        destination:
          allOf:
            - $ref: '#/components/schemas/SweepRuleDestination'
          description: Where funds that settle into this account are swept.
        minimumAmount:
          allOf:
            - $ref: '#/components/schemas/CurrencyAmount'
          description: '**In this rule-based account''s currency, not the destination''s.** The smallest balance the corridor to the destination can carry; a settled balance below it is not swept. Zero means no floor, which is the case for a same-currency internal destination — a book transfer with no rail, fee or conversion to justify one. Configuration rather than a moving estimate, so there is nothing to re-poll.'
        maximumAmount:
          allOf:
            - $ref: '#/components/schemas/CurrencyAmount'
          description: '**In this rule-based account''s currency, not the destination''s.** The largest balance the corridor to the destination can carry; a settled balance above it is not swept. Null means no ceiling.'
        purposeOfPayment:
          allOf:
            - $ref: '#/components/schemas/PurposeOfPayment'
          description: The purpose of payment applied to each sweep.
          example: SELF
        description:
          type: string
          description: Free-form description recorded on each sweep. Not delivered to the recipient.
          example: Rent sweep
        remittanceInformation:
          type: string
          description: Free-form information that travels with each sweep to the recipient.
          example: Unit 4B March
        platformFeeOverride:
          allOf:
            - $ref: '#/components/schemas/PlatformFeeOverride'
          description: Fee terms applied to every sweep this rule drives. Null when the platform's configured fees apply.
    CardCapabilities:
      type: object
      description: Actions supported by the card program associated with this resource.
      required:
        - supportsSpendLimits
        - supportsSpendLimitsAtIssuance
        - supportsTransactionCountLimit
        - supports3dSecurePassword
        - supportsPanReveal
        - supportsDigitalWalletTokenization
      properties:
        supportsSpendLimits:
          type: boolean
          description: Whether a card in this program can have `maxSpendPerTransaction` and `maxSpendPerDay` at all. On card programs where the card issuer makes authorization decisions, these limits can only be set after issuance through `PATCH /cards/{id}`. Check `supportsSpendLimitsAtIssuance` to determine whether you can supply them when issuing a card.
          example: true
        supportsSpendLimitsAtIssuance:
          type: boolean
          description: Whether `maxSpendPerTransaction` and `maxSpendPerDay` may be supplied on `POST /cards`. This is true for card programs where Grid makes the authorization decision and false for card programs where the card issuer makes authorization decisions.
          example: true
        supportsTransactionCountLimit:
          type: boolean
          description: Whether cards in this program accept `maxTransactionsPerDay`.
          example: true
        supports3dSecurePassword:
          type: boolean
          description: Whether cards in this program accept a caller-supplied `threeDSecurePassword`.
          example: false
        supportsPanReveal:
          type: boolean
          description: Whether cards in this program can be revealed through `POST /cards/{id}/reveal`.
          example: true
        supportsDigitalWalletTokenization:
          type: boolean
          description: Whether cards in this program can be added to Apple Pay, Google Pay, or Samsung Pay from your app through `POST /cards/{id}/tokenize`. Manual entry into a wallet works regardless of this flag.
          example: true
    InternalAccount:
      type: object
      required:
        - id
        - type
        - status
        - balance
        - totalBalance
        - fundingPaymentInstructions
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: The ID of the internal account
          example: InternalAccount:12dcbd6-dced-4ec4-b756-3c3a9ea3d123
        customerId:
          type: string
          description: The ID of the customer associated with the internal account. If this field is empty, the internal account belongs to the platform.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        type:
          $ref: '#/components/schemas/InternalAccountType'
        status:
          $ref: '#/components/schemas/InternalAccountStatus'
        balance:
          $ref: '#/components/schemas/CurrencyAmount'
          description: The balance available to spend, excluding pending and held funds
        totalBalance:
          $ref: '#/components/schemas/CurrencyAmount'
          description: The total balance, including pending and held funds
        fundingPaymentInstructions:
          type: array
          description: Payment instructions for funding the account. Empty while funding is withheld. Only bank (`USD_ACCOUNT`, `EUR_ACCOUNT`, `SWIFT_ACCOUNT`) and network wallet types appear here.
          items:
            $ref: '#/components/schemas/PaymentInstructions'
        onBehalfOfCustomerId:
          type: string
          description: The customer whose payments this platform-owned account receives. Only set by regulated financial institutions. Omitted for accounts that name no such customer, which includes every account owned by a customer.
          example: Customer:019542f5-b3e7-1d02-0000-000000000002
        label:
          type: string
          maxLength: 255
          description: The platform-supplied label recorded when the account was created. Null for accounts that carry none.
          example: invoice-4417
        sweepRule:
          allOf:
            - $ref: '#/components/schemas/SweepRule'
          description: The routing rule attached to this account. Null for accounts that carry no rule, which is every account other than a `RULE_BASED` one.
        privateEnabled:
          type: boolean
          description: Whether wallet privacy is enabled for the Embedded Wallet. Only present for `EMBEDDED_WALLET` internal accounts.
          example: true
        cardCapabilities:
          $ref: '#/components/schemas/CardCapabilities'
          description: Actions supported for a card issued now with this account as its funding source. They can change if the platform's card routing changes and do not describe cards already issued using the account. The funding source a create request supplies selects the issuer and therefore the resulting card's capabilities; read this field from that account. Absent when this account cannot fund a card.
        createdAt:
          type: string
          format: date-time
          description: Timestamp when the internal account was created
          example: '2025-10-03T12:30:00Z'
        updatedAt:
          type: string
          format: date-time
          description: Timestamp when the internal account was last updated
          example: '2025-10-03T12:30:00Z'
    Error403:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | FORBIDDEN | Insufficient permissions |
            | USER_NOT_READY | Customer exists but is not ready for operation |
            | COUNTERPARTY_NOT_ALLOWED | Counterparty has not been enabled for your account |
            | VELOCITY_LIMIT_EXCEEDED | Counterparty has exceeded velocity limits |
            | END_USER_TERMS_NOT_ACCEPTED | Customer has not accepted the End User Terms |
            | CUSTOMER_NOT_VERIFIED | The customer is not verified and cannot perform this action |
            | SANCTION_BLOCKED | Blocked by sanction screening |
          enum:
            - FORBIDDEN
            - USER_NOT_READY
            - COUNTERPARTY_NOT_ALLOWED
            - VELOCITY_LIMIT_EXCEEDED
            - END_USER_TERMS_NOT_ACCEPTED
            - CUSTOMER_NOT_VERIFIED
            - SANCTION_BLOCKED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    InternalAccountListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of internal accounts matching the filter criteria
          items:
            $ref: '#/components/schemas/InternalAccount'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of customers matching the criteria (excluding pagination)
    PlatformInternalAccountListResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          description: List of internal accounts matching the filter criteria
          items:
            $ref: '#/components/schemas/InternalAccount'
    ExternalAccountStatus:
      type: string
      enum:
        - PENDING
        - ACTIVE
        - PENDING_OWNERSHIP_VERIFICATION
        - UNVERIFIED
        - UNDER_REVIEW
        - INACTIVE
      description: |
        Status of an external account.

        `PENDING_OWNERSHIP_VERIFICATION`: the wallet's ownership must be verified
        (e.g. under the EU Travel Rule) before unrestricted use; transfers below
        regulatory thresholds are still allowed. `UNVERIFIED`: the most recent
        verification attempt failed; start a new challenge to retry. Successful
        verification moves the account to `ACTIVE`.
    OwnershipType:
      type: string
      enum:
        - FIRST_PARTY
        - THIRD_PARTY
      description: Whether the external account belongs to the customer themselves (`FIRST_PARTY`) or to someone else (`THIRD_PARTY`). Required when creating self-custody crypto wallet external accounts on platforms subject to counterparty requirements — for example, under the EU Travel Rule or similar requirements in other regions; recommended for all other accounts, where providing it can unlock additional capabilities and smoother compliance handling.
      example: FIRST_PARTY
    BeneficiaryVerificationStatus:
      type: string
      enum:
        - MATCHED
        - PARTIAL_MATCH
        - NOT_MATCHED
        - UNSUPPORTED
        - CHECKED_BY_RECEIVING_FI
        - PENDING
      description: |
        The result of verifying the beneficiary name against the account holder name.

        | Status | Description |
        |--------|-------------|
        | `MATCHED` | The beneficiary name is an exact match |
        | `PARTIAL_MATCH` | The beneficiary name is a fuzzy match |
        | `NOT_MATCHED` | The beneficiary name does not match |
        | `UNSUPPORTED` | The payment rail does not support name verification |
        | `CHECKED_BY_RECEIVING_FI` | Verification is deferred to the receiving financial institution (e.g. ACH) |
        | `PENDING` | Verification is still in progress |
    BeneficiaryVerifiedData:
      type: object
      properties:
        fullName:
          type: string
          description: The verified full name of the account holder as returned by the payment rail
          example: John Doe
    ExternalAccountType:
      type: string
      enum:
        - AED_ACCOUNT
        - BDT_ACCOUNT
        - BRL_ACCOUNT
        - BWP_ACCOUNT
        - CAD_ACCOUNT
        - COP_ACCOUNT
        - DKK_ACCOUNT
        - EGP_ACCOUNT
        - EUR_ACCOUNT
        - GBP_ACCOUNT
        - GHS_ACCOUNT
        - GTQ_ACCOUNT
        - HKD_ACCOUNT
        - HTG_ACCOUNT
        - IDR_ACCOUNT
        - INR_ACCOUNT
        - JMD_ACCOUNT
        - KES_ACCOUNT
        - MWK_ACCOUNT
        - MXN_ACCOUNT
        - MYR_ACCOUNT
        - NGN_ACCOUNT
        - PHP_ACCOUNT
        - PKR_ACCOUNT
        - RWF_ACCOUNT
        - SGD_ACCOUNT
        - SLV_ACCOUNT
        - THB_ACCOUNT
        - TZS_ACCOUNT
        - UGX_ACCOUNT
        - USD_ACCOUNT
        - VND_ACCOUNT
        - XAF_ACCOUNT
        - XOF_ACCOUNT
        - ZAR_ACCOUNT
        - ZMW_ACCOUNT
        - SWIFT_ACCOUNT
        - BASE_WALLET
        - ETHEREUM_WALLET
        - LIGHTNING
        - PLASMA_WALLET
        - ARBITRUM_WALLET
        - POLYGON_WALLET
        - SOLANA_WALLET
        - SPARK_WALLET
        - TRON_WALLET
        - CNY_ACCOUNT
        - ILS_ACCOUNT
        - TRY_ACCOUNT
      description: Type of external account or wallet
      example: AED_ACCOUNT
    BaseExternalAccountInfo:
      type: object
      required:
        - accountType
      properties:
        accountType:
          $ref: '#/components/schemas/ExternalAccountType'
    AedAccountInfoBase:
      type: object
      required:
        - accountType
        - iban
      properties:
        accountType:
          type: string
          enum:
            - AED_ACCOUNT
        iban:
          type: string
          description: UAE IBAN (23 characters, starting with AE)
          example: AE070331234567890123456
          minLength: 23
          maxLength: 23
          pattern: ^AE[0-9]{21}$
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: EBILAEAD
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
      example:
        accountType: AED_ACCOUNT
        iban: AE070331234567890123456
        swiftCode: EBILAEAD
    AedAccountInfo:
      allOf:
        - $ref: '#/components/schemas/AedAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    AedBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - address
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    AedBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - address
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    AedExternalAccountInfo:
      title: AED Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/AedAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/AedBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/AedBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/AedBeneficiary'
                  BUSINESS: '#/components/schemas/AedBusinessBeneficiary'
    BdtAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
      description: |-
        Required fields depend on the selected paymentRails:
        - BANK_TRANSFER: accountNumber, bankName
        - MOBILE_MONEY: bankName, phoneNumber
      properties:
        accountType:
          type: string
          enum:
            - BDT_ACCOUNT
        accountNumber:
          type: string
          description: The account number of the bank
          minLength: 1
          maxLength: 34
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: BRAC Bank
        branchCode:
          type: string
          description: The branch code
          minLength: 5
          maxLength: 5
          pattern: ^[0-9]{5}$
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: DEUTDEFF
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
      example:
        accountType: BDT_ACCOUNT
        accountNumber: '1234567890'
        bankName: BRAC Bank
        branchCode: '11111'
        swiftCode: DEUTDEFF
        phoneNumber: '+1234567890'
    BdtAccountInfo:
      allOf:
        - $ref: '#/components/schemas/BdtAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
                  - MOBILE_MONEY
    BdtBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    BusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        registrationNumber:
          type: string
          maxLength: 100
          description: The registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    BdtExternalAccountInfo:
      title: BDT Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/BdtAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/BdtBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/BdtBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    BrlAccountInfoBase:
      type: object
      required:
        - accountType
        - pixKey
        - pixKeyType
        - taxId
      properties:
        accountType:
          type: string
          enum:
            - BRL_ACCOUNT
        pixKey:
          type: string
          description: The PIX key (email, phone, CPF, CNPJ, or random)
          minLength: 1
          maxLength: 77
        pixKeyType:
          type: string
          description: The type of PIX key
          enum:
            - CPF
            - CNPJ
            - EMAIL
            - PHONE
            - RANDOM
        taxId:
          type: string
          description: The tax ID (CPF or CNPJ)
          minLength: 11
          maxLength: 14
          pattern: ^[0-9]{11,14}$
      example:
        accountType: BRL_ACCOUNT
        pixKey: user@example.com
        pixKeyType: CPF
        taxId: '11111111111'
    BrlAccountInfo:
      allOf:
        - $ref: '#/components/schemas/BrlAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - PIX
    BrlBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    BrlBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    BrlExternalAccountInfo:
      title: BRL Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/BrlAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/BrlBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BrlBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/BrlBeneficiary'
                  BUSINESS: '#/components/schemas/BrlBusinessBeneficiary'
    BwpAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
      properties:
        accountType:
          type: string
          enum:
            - BWP_ACCOUNT
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
      example:
        accountType: BWP_ACCOUNT
        phoneNumber: '+1234567890'
        provider: Example Provider
    BwpAccountInfo:
      allOf:
        - $ref: '#/components/schemas/BwpAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    BwpBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    BwpBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    BwpExternalAccountInfo:
      title: BWP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/BwpAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/BwpBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BwpBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/BwpBeneficiary'
                  BUSINESS: '#/components/schemas/BwpBusinessBeneficiary'
    CadAccountInfoBase:
      type: object
      required:
        - accountType
        - bankCode
        - branchCode
        - accountNumber
      properties:
        accountType:
          type: string
          enum:
            - CAD_ACCOUNT
        bankCode:
          type: string
          description: Canadian financial institution number (3 digits)
          example: '001'
          minLength: 3
          maxLength: 3
          pattern: ^[0-9]{3}$
        branchCode:
          type: string
          description: Transit number identifying the branch (5 digits)
          example: '00012'
          minLength: 5
          maxLength: 5
          pattern: ^[0-9]{5}$
        accountNumber:
          type: string
          description: Bank account number (7-12 digits)
          example: '1234567'
          minLength: 7
          maxLength: 12
          pattern: ^[0-9]{7,12}$
    CadAccountInfo:
      allOf:
        - $ref: '#/components/schemas/CadAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    CadBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The registration number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    CadExternalAccountInfo:
      title: CAD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/CadAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/CadBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/CadBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    CopAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
      description: |-
        Required fields depend on the selected paymentRails:
        - BANK_TRANSFER: accountNumber, bankAccountType, bankName
        - MOBILE_MONEY: bankName, phoneNumber
      properties:
        accountType:
          type: string
          enum:
            - COP_ACCOUNT
        accountNumber:
          type: string
          description: The account number of the bank
          minLength: 1
          maxLength: 34
        bankAccountType:
          type: string
          description: The bank account type
          enum:
            - CHECKING
            - SAVINGS
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Banco de Colombia (Bancolombia)
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
      example:
        accountType: COP_ACCOUNT
        accountNumber: '1234567890'
        bankAccountType: CHECKING
        bankName: Banco de Colombia (Bancolombia)
        phoneNumber: '+1234567890'
    CopAccountInfo:
      allOf:
        - $ref: '#/components/schemas/CopAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
                  - MOBILE_MONEY
    CopBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
        documentType:
          type: string
          maxLength: 32
          description: Identity document type (Colombia)
          enum:
            - CC
            - CE
            - TI
            - NIT
            - PP
        documentNumber:
          type: string
          maxLength: 50
          description: The identity document number
          minLength: 1
    CopBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 64
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    CopExternalAccountInfo:
      title: COP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/CopAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/CopBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/CopBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/CopBeneficiary'
                  BUSINESS: '#/components/schemas/CopBusinessBeneficiary'
    DkkAccountInfoBase:
      type: object
      required:
        - accountType
        - iban
      properties:
        accountType:
          type: string
          enum:
            - DKK_ACCOUNT
        iban:
          type: string
          description: Danish IBAN (18 characters, starting with DK)
          example: DK5000400040116243
          minLength: 18
          maxLength: 18
          pattern: ^DK[0-9]{16}$
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: DABADKKK
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
      example:
        accountType: DKK_ACCOUNT
        iban: DK5000400040116243
        swiftCode: DABADKKK
    DkkAccountInfo:
      allOf:
        - $ref: '#/components/schemas/DkkAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - SEPA
                  - SEPA_INSTANT
    DkkBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    DkkBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    DkkExternalAccountInfo:
      title: DKK Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/DkkAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/DkkBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/DkkBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/DkkBeneficiary'
                  BUSINESS: '#/components/schemas/DkkBusinessBeneficiary'
    EgpAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
      description: |-
        Required fields depend on the selected paymentRails:
        - BANK_TRANSFER: bankName, iban
        - MOBILE_MONEY: bankName, phoneNumber
      properties:
        accountType:
          type: string
          enum:
            - EGP_ACCOUNT
        iban:
          type: string
          description: Egyptian IBAN (29 characters, starting with EG)
          example: EG380019000500000000263180002
          minLength: 29
          maxLength: 29
          pattern: ^EG[0-9]{27}$
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: BANQUE MISR
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
      example:
        accountType: EGP_ACCOUNT
        iban: EG380019000500000000263180002
        bankName: BANQUE MISR
        phoneNumber: '+1234567890'
    EgpAccountInfo:
      allOf:
        - $ref: '#/components/schemas/EgpAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
                  - MOBILE_MONEY
    EgpBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    EgpBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - address
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    EgpExternalAccountInfo:
      title: EGP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/EgpAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/EgpBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/EgpBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/EgpBeneficiary'
                  BUSINESS: '#/components/schemas/EgpBusinessBeneficiary'
    EurBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - countryOfResidence
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    EurBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - countryOfResidence
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    EurExternalAccountInfo:
      title: EUR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/EurAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/EurBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/EurBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/EurBeneficiary'
                  BUSINESS: '#/components/schemas/EurBusinessBeneficiary'
    GbpAccountInfoBase:
      type: object
      required:
        - accountType
        - sortCode
        - accountNumber
      properties:
        accountType:
          type: string
          enum:
            - GBP_ACCOUNT
        sortCode:
          type: string
          description: The UK sort code
          example: '123456'
          minLength: 6
          maxLength: 6
          pattern: ^[0-9]{6}$
        accountNumber:
          type: string
          description: UK bank account number (8 digits)
          minLength: 8
          maxLength: 8
          example: '12345678'
          pattern: ^[0-9]{8}$
      example:
        accountType: GBP_ACCOUNT
        sortCode: '123456'
        accountNumber: '12345678'
    GbpAccountInfo:
      allOf:
        - $ref: '#/components/schemas/GbpAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - FASTER_PAYMENTS
    GbpBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    GbpBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    GbpExternalAccountInfo:
      title: GBP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/GbpAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/GbpBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/GbpBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/GbpBeneficiary'
                  BUSINESS: '#/components/schemas/GbpBusinessBeneficiary'
    GhsAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
      description: |-
        Required fields depend on the selected paymentRails:
        - BANK_TRANSFER: accountNumber, bankName
        - MOBILE_MONEY: bankName, phoneNumber
      properties:
        accountType:
          type: string
          enum:
            - GHS_ACCOUNT
        accountNumber:
          type: string
          description: The account number of the bank
          minLength: 1
          maxLength: 34
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Gcb Bank Ltd
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
      example:
        accountType: GHS_ACCOUNT
        accountNumber: '1234567890'
        bankName: Gcb Bank Ltd
        phoneNumber: '+1234567890'
    GhsAccountInfo:
      allOf:
        - $ref: '#/components/schemas/GhsAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
                  - MOBILE_MONEY
    GhsBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    GhsExternalAccountInfo:
      title: GHS Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/GhsAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/GhsBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/GhsBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    GtqAccountInfoBase:
      type: object
      required:
        - accountType
        - accountNumber
        - bankAccountType
        - bankName
      properties:
        accountType:
          type: string
          enum:
            - GTQ_ACCOUNT
        accountNumber:
          type: string
          description: The account number of the bank
          minLength: 1
          maxLength: 34
        bankAccountType:
          type: string
          description: The bank account type
          enum:
            - CHECKING
            - SAVINGS
        bankName:
          type: string
          description: The name of the beneficiary's bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Banco Industrial
      example:
        accountType: GTQ_ACCOUNT
        accountNumber: '1234567890'
        bankAccountType: CHECKING
        bankName: Banco Industrial
    GtqAccountInfo:
      allOf:
        - $ref: '#/components/schemas/GtqAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    GtqBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - countryOfResidence
        - fullName
        - phoneNumber
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    GtqExternalAccountInfo:
      title: GTQ Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/GtqAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/GtqBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/GtqBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    HkdAccountInfoBase:
      type: object
      required:
        - accountType
        - accountNumber
        - swiftCode
      properties:
        accountType:
          type: string
          enum:
            - HKD_ACCOUNT
        bankName:
          type: string
          description: Name of the beneficiary's bank. When omitted, resolved from swiftCode via the payout partner bank directory at account creation. Not checked against `GET /discoveries`.
          minLength: 1
          maxLength: 255
          example: HSBC Hong Kong
        accountNumber:
          type: string
          description: Hong Kong bank account number
          minLength: 1
          maxLength: 34
          example: '123456789012'
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: HSBCHKHHHKH
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
      example:
        accountType: HKD_ACCOUNT
        bankName: HSBC Hong Kong
        accountNumber: '123456789012'
        swiftCode: HSBCHKHHHKH
    HkdAccountInfo:
      allOf:
        - $ref: '#/components/schemas/HkdAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    HkdBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    HkdBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    HkdExternalAccountInfo:
      title: HKD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/HkdAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/HkdBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/HkdBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/HkdBeneficiary'
                  BUSINESS: '#/components/schemas/HkdBusinessBeneficiary'
    HtgAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
      properties:
        accountType:
          type: string
          enum:
            - HTG_ACCOUNT
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
      example:
        accountType: HTG_ACCOUNT
        phoneNumber: '+1234567890'
    HtgAccountInfo:
      allOf:
        - $ref: '#/components/schemas/HtgAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    HtgBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    HtgExternalAccountInfo:
      title: HTG Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/HtgAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/HtgBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/HtgBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    IdrAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
        - accountNumber
        - swiftCode
        - phoneNumber
      properties:
        accountType:
          type: string
          enum:
            - IDR_ACCOUNT
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Bank Central Asia (BCA)
        accountNumber:
          type: string
          description: Indonesian bank account number
          minLength: 1
          maxLength: 34
          example: '1234567890'
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: CENAIDJA
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
        phoneNumber:
          type: string
          description: Indonesian phone number for e-wallet payments
          example: '+6281234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+62[0-9]{9,12}$
      example:
        accountType: IDR_ACCOUNT
        bankName: Bank Central Asia (BCA)
        accountNumber: '1234567890'
        swiftCode: CENAIDJA
        phoneNumber: '+6281234567890'
    IdrAccountInfo:
      allOf:
        - $ref: '#/components/schemas/IdrAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    IdrBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    IdrBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    IdrExternalAccountInfo:
      title: IDR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/IdrAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/IdrBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/IdrBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/IdrBeneficiary'
                  BUSINESS: '#/components/schemas/IdrBusinessBeneficiary'
    InrAccountInfoBase:
      type: object
      required:
        - accountType
      description: |-
        Required fields depend on the selected paymentRails:
        - NEFT: accountNumber, ifsc, rail
        - RTGS: accountNumber, ifsc, rail
        - UPI: vpa
      properties:
        accountType:
          type: string
          enum:
            - INR_ACCOUNT
        vpa:
          type: string
          description: The UPI Virtual Payment Address
          example: user@upi
          minLength: 3
          maxLength: 255
          pattern: ^[a-zA-Z0-9.\-_]+@[a-zA-Z0-9]+$
        accountNumber:
          type: string
          description: Indian bank account number (9–18 digits)
          minLength: 9
          maxLength: 18
          example: '000111222333'
          pattern: ^[0-9]{9,18}$
        ifsc:
          type: string
          description: The Indian Financial System Code (IFSC) of the beneficiary's bank branch (NEFT/RTGS)
          example: HDFC0001234
          minLength: 11
          maxLength: 11
          pattern: ^[A-Z]{4}0[A-Z0-9]{6}$
        rail:
          type: string
          description: 'The payment rail to route the payout over: NEFT or RTGS. Omitted, the payout resolves it by amount.'
          minLength: 1
          maxLength: 32
          example: NEFT
      example:
        accountType: INR_ACCOUNT
        vpa: user@upi
        accountNumber: '000111222333'
        ifsc: HDFC0001234
        rail: NEFT
    InrAccountInfo:
      allOf:
        - $ref: '#/components/schemas/InrAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - UPI
                  - NEFT
                  - RTGS
    InrBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    InrBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    InrExternalAccountInfo:
      title: INR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/InrAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/InrBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/InrBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/InrBeneficiary'
                  BUSINESS: '#/components/schemas/InrBusinessBeneficiary'
    JmdAccountInfoBase:
      type: object
      required:
        - accountType
        - accountNumber
        - branchCode
        - bankAccountType
        - bankName
      properties:
        accountType:
          type: string
          enum:
            - JMD_ACCOUNT
        accountNumber:
          type: string
          description: The account number of the bank
          minLength: 1
          maxLength: 34
        branchCode:
          type: string
          description: The branch code
          minLength: 5
          maxLength: 5
          pattern: ^[0-9]{5}$
        bankAccountType:
          type: string
          description: The bank account type
          enum:
            - CHECKING
            - SAVINGS
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: National Commercial Bank Ja Ltd
      example:
        accountType: JMD_ACCOUNT
        accountNumber: '1234567890'
        branchCode: '11111'
        bankAccountType: CHECKING
        bankName: National Commercial Bank Ja Ltd
    JmdAccountInfo:
      allOf:
        - $ref: '#/components/schemas/JmdAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    JmdBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - address
        - fullName
        - phoneNumber
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    JmdExternalAccountInfo:
      title: JMD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/JmdAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/JmdBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/JmdBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    KesAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
      properties:
        accountType:
          type: string
          enum:
            - KES_ACCOUNT
        phoneNumber:
          type: string
          description: Kenyan mobile money phone number
          example: '+254712345678'
          minLength: 7
          maxLength: 15
          pattern: ^\+254[0-9]{9}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
      example:
        accountType: KES_ACCOUNT
        phoneNumber: '+254712345678'
        provider: Example Provider
    KesAccountInfo:
      allOf:
        - $ref: '#/components/schemas/KesAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    KesBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    KesBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    KesExternalAccountInfo:
      title: KES Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/KesAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/KesBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/KesBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/KesBeneficiary'
                  BUSINESS: '#/components/schemas/KesBusinessBeneficiary'
    MwkAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
      properties:
        accountType:
          type: string
          enum:
            - MWK_ACCOUNT
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
      example:
        accountType: MWK_ACCOUNT
        phoneNumber: '+1234567890'
        provider: Example Provider
    MwkAccountInfo:
      allOf:
        - $ref: '#/components/schemas/MwkAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    MwkBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    MwkBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    MwkExternalAccountInfo:
      title: MWK Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/MwkAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/MwkBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/MwkBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/MwkBeneficiary'
                  BUSINESS: '#/components/schemas/MwkBusinessBeneficiary'
    MxnBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    MxnBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    MxnExternalAccountInfo:
      title: MXN Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/MxnAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/MxnBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/MxnBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/MxnBeneficiary'
                  BUSINESS: '#/components/schemas/MxnBusinessBeneficiary'
    MyrAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
        - accountNumber
        - swiftCode
      properties:
        accountType:
          type: string
          enum:
            - MYR_ACCOUNT
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Maybank
        accountNumber:
          type: string
          description: Malaysian bank account number
          minLength: 1
          maxLength: 34
          example: '1234567890'
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: MABORUMMYYY
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
      example:
        accountType: MYR_ACCOUNT
        bankName: Maybank
        accountNumber: '1234567890'
        swiftCode: MABORUMMYYY
    MyrAccountInfo:
      allOf:
        - $ref: '#/components/schemas/MyrAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    MyrBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    MyrBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    MyrExternalAccountInfo:
      title: MYR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/MyrAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/MyrBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/MyrBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/MyrBeneficiary'
                  BUSINESS: '#/components/schemas/MyrBusinessBeneficiary'
    NgnAccountInfoBase:
      type: object
      required:
        - accountType
        - accountNumber
        - bankName
      properties:
        accountType:
          type: string
          enum:
            - NGN_ACCOUNT
        accountNumber:
          type: string
          description: Nigerian bank account number
          minLength: 10
          maxLength: 10
          example: '0123456789'
          pattern: ^[0-9]{10}$
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Access Bank
      example:
        accountType: NGN_ACCOUNT
        accountNumber: '0123456789'
        bankName: Access Bank
    NgnAccountInfo:
      allOf:
        - $ref: '#/components/schemas/NgnAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    NgnBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    NgnBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    NgnExternalAccountInfo:
      title: NGN Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/NgnAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/NgnBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/NgnBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/NgnBeneficiary'
                  BUSINESS: '#/components/schemas/NgnBusinessBeneficiary'
    PhpAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
        - accountNumber
      properties:
        accountType:
          type: string
          enum:
            - PHP_ACCOUNT
        rail:
          type: string
          description: 'The payment rail to route the payout over: INSTAPAY or PESONET. Omitted, the payout resolves it by amount.'
          minLength: 1
          maxLength: 32
          example: INSTAPAY
        bankName:
          type: string
          description: Name of the beneficiary's bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Bank of the Philippine Islands (BPI)
        accountNumber:
          type: string
          description: Bank account number
          minLength: 8
          maxLength: 16
          example: '001234567890'
          pattern: ^[0-9]{8,16}$
      example:
        accountType: PHP_ACCOUNT
        rail: INSTAPAY
        bankName: Bank of the Philippine Islands (BPI)
        accountNumber: '001234567890'
    PhpAccountInfo:
      allOf:
        - $ref: '#/components/schemas/PhpAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    PhpBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    PhpBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    PhpExternalAccountInfo:
      title: PHP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/PhpAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/PhpBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/PhpBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/PhpBeneficiary'
                  BUSINESS: '#/components/schemas/PhpBusinessBeneficiary'
    PkrAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
      description: |-
        Required fields depend on the selected paymentRails:
        - BANK_TRANSFER: accountNumber, bankName
        - MOBILE_MONEY: bankName, phoneNumber
      properties:
        accountType:
          type: string
          enum:
            - PKR_ACCOUNT
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Habib Bank Limited
        accountNumber:
          type: string
          description: The account number of the bank
          minLength: 1
          maxLength: 34
        iban:
          type: string
          description: Pakistani IBAN (24 characters, starting with PK)
          example: PK36SCBL0000001123456702
          minLength: 24
          maxLength: 24
          pattern: ^PK[0-9]{2}[A-Z]{4}[0-9]{16}$
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
      example:
        accountType: PKR_ACCOUNT
        bankName: Habib Bank Limited
        accountNumber: '1234567890'
        iban: PK36SCBL0000001123456702
        phoneNumber: '+1234567890'
    PkrAccountInfo:
      allOf:
        - $ref: '#/components/schemas/PkrAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
                  - MOBILE_MONEY
    PkrBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    PkrExternalAccountInfo:
      title: PKR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/PkrAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/PkrBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/PkrBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    RwfAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
      properties:
        accountType:
          type: string
          enum:
            - RWF_ACCOUNT
        phoneNumber:
          type: string
          description: Rwandan mobile money phone number
          example: '+250781234567'
          minLength: 7
          maxLength: 15
          pattern: ^\+250[0-9]{9}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
      example:
        accountType: RWF_ACCOUNT
        phoneNumber: '+250781234567'
        provider: Example Provider
    RwfAccountInfo:
      allOf:
        - $ref: '#/components/schemas/RwfAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    RwfBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    RwfBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    RwfExternalAccountInfo:
      title: RWF Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/RwfAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/RwfBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/RwfBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/RwfBeneficiary'
                  BUSINESS: '#/components/schemas/RwfBusinessBeneficiary'
    SgdAccountInfoBase:
      type: object
      required:
        - accountType
        - accountNumber
        - swiftCode
      properties:
        accountType:
          type: string
          enum:
            - SGD_ACCOUNT
        bankName:
          type: string
          description: Name of the beneficiary's bank. When omitted, resolved from swiftCode via the payout partner bank directory at account creation. Not checked against `GET /discoveries`.
          minLength: 1
          maxLength: 255
          example: DBS Bank Ltd
        accountNumber:
          type: string
          description: Bank account number
          minLength: 1
          maxLength: 34
          example: '0123456789'
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: DBSSSGSG
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
      example:
        accountType: SGD_ACCOUNT
        bankName: DBS Bank Ltd
        accountNumber: '0123456789'
        swiftCode: DBSSSGSG
    SgdAccountInfo:
      allOf:
        - $ref: '#/components/schemas/SgdAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - PAYNOW
                  - FAST
                  - BANK_TRANSFER
    SgdBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    SgdBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    SgdExternalAccountInfo:
      title: SGD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/SgdAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/SgdBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/SgdBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/SgdBeneficiary'
                  BUSINESS: '#/components/schemas/SgdBusinessBeneficiary'
    SlvAccountInfoBase:
      type: object
      required:
        - accountType
      description: |-
        Required fields depend on the selected paymentRails:
        - BANK_TRANSFER: bankAccountType, accountNumber
        - MOBILE_MONEY: phoneNumber
      properties:
        accountType:
          type: string
          enum:
            - SLV_ACCOUNT
        bankName:
          type: string
          description: The name of the bank (BANK_TRANSFER only)
          minLength: 1
          maxLength: 255
        accountNumber:
          type: string
          description: The account number of the bank (BANK_TRANSFER only)
          minLength: 1
          maxLength: 34
        bankAccountType:
          type: string
          description: The bank account type (BANK_TRANSFER only)
          enum:
            - CHECKING
            - SAVINGS
        phoneNumber:
          type: string
          description: The phone number in international format (MOBILE_MONEY only — e.g. Tigo Money)
          example: '+50312345678'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
      example:
        accountType: SLV_ACCOUNT
        bankName: Banco Cuscatlan
        accountNumber: '0123456789'
        bankAccountType: CHECKING
    SlvAccountInfo:
      allOf:
        - $ref: '#/components/schemas/SlvAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
                  - MOBILE_MONEY
    SlvBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    SlvExternalAccountInfo:
      title: SLV Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/SlvAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/SlvBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/SlvBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    ThbAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
        - accountNumber
        - swiftCode
      properties:
        accountType:
          type: string
          enum:
            - THB_ACCOUNT
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Bangkok Bank Public Company Limited
        accountNumber:
          type: string
          description: Thai bank account number
          minLength: 1
          maxLength: 34
          example: '1234567890'
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: BKKBTHBK
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
      example:
        accountType: THB_ACCOUNT
        bankName: Bangkok Bank Public Company Limited
        accountNumber: '1234567890'
        swiftCode: BKKBTHBK
    ThbAccountInfo:
      allOf:
        - $ref: '#/components/schemas/ThbAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    ThbBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    ThbBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    ThbExternalAccountInfo:
      title: THB Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/ThbAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/ThbBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/ThbBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/ThbBeneficiary'
                  BUSINESS: '#/components/schemas/ThbBusinessBeneficiary'
    TzsAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
      properties:
        accountType:
          type: string
          enum:
            - TZS_ACCOUNT
        phoneNumber:
          type: string
          description: Tanzanian mobile money phone number
          example: '+255712345678'
          minLength: 7
          maxLength: 15
          pattern: ^\+255[0-9]{9}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
      example:
        accountType: TZS_ACCOUNT
        phoneNumber: '+255712345678'
        provider: Example Provider
    TzsAccountInfo:
      allOf:
        - $ref: '#/components/schemas/TzsAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    TzsBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    TzsBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    TzsExternalAccountInfo:
      title: TZS Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/TzsAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/TzsBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/TzsBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/TzsBeneficiary'
                  BUSINESS: '#/components/schemas/TzsBusinessBeneficiary'
    UgxAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
      properties:
        accountType:
          type: string
          enum:
            - UGX_ACCOUNT
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
      example:
        accountType: UGX_ACCOUNT
        phoneNumber: '+1234567890'
        provider: Example Provider
    UgxAccountInfo:
      allOf:
        - $ref: '#/components/schemas/UgxAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    UgxBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    UgxBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    UgxExternalAccountInfo:
      title: UGX Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/UgxAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/UgxBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/UgxBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/UgxBeneficiary'
                  BUSINESS: '#/components/schemas/UgxBusinessBeneficiary'
    UsdBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    UsdBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    UsdExternalAccountInfo:
      title: USD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/UsdAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/UsdBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/UsdBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/UsdBeneficiary'
                  BUSINESS: '#/components/schemas/UsdBusinessBeneficiary'
    VndAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
        - accountNumber
        - swiftCode
      properties:
        accountType:
          type: string
          enum:
            - VND_ACCOUNT
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Vietcombank
        accountNumber:
          type: string
          description: Vietnamese bank account number
          minLength: 1
          maxLength: 34
          example: '1234567890'
        swiftCode:
          type: string
          description: The SWIFT/BIC code of the bank
          example: BFTVVNVX
          minLength: 8
          maxLength: 11
          pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$
      example:
        accountType: VND_ACCOUNT
        bankName: Vietcombank
        accountNumber: '1234567890'
        swiftCode: BFTVVNVX
    VndAccountInfo:
      allOf:
        - $ref: '#/components/schemas/VndAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    VndBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    VndBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    VndExternalAccountInfo:
      title: VND Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/VndAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/VndBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/VndBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/VndBeneficiary'
                  BUSINESS: '#/components/schemas/VndBusinessBeneficiary'
    XafAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
        - region
      properties:
        accountType:
          type: string
          enum:
            - XAF_ACCOUNT
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
        region:
          type: string
          description: Country code within the Central African CFA franc zone
          minLength: 2
          maxLength: 2
          pattern: ^[A-Z]{2}$
          enum:
            - CM
            - CG
      example:
        accountType: XAF_ACCOUNT
        phoneNumber: '+1234567890'
        provider: Example Provider
        region: CM
    XafAccountInfo:
      allOf:
        - $ref: '#/components/schemas/XafAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    XafBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    XafBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    XafExternalAccountInfo:
      title: XAF Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/XafAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/XafBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/XafBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/XafBeneficiary'
                  BUSINESS: '#/components/schemas/XafBusinessBeneficiary'
    XofAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
        - region
      properties:
        accountType:
          type: string
          enum:
            - XOF_ACCOUNT
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
        region:
          type: string
          description: Country code within the West African CFA franc zone
          minLength: 2
          maxLength: 2
          pattern: ^[A-Z]{2}$
          enum:
            - BJ
            - CI
            - SN
            - TG
      example:
        accountType: XOF_ACCOUNT
        phoneNumber: '+1234567890'
        provider: Example Provider
        region: BJ
    XofAccountInfo:
      allOf:
        - $ref: '#/components/schemas/XofAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    XofBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    XofBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    XofExternalAccountInfo:
      title: XOF Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/XofAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/XofBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/XofBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/XofBeneficiary'
                  BUSINESS: '#/components/schemas/XofBusinessBeneficiary'
    ZarAccountInfoBase:
      type: object
      required:
        - accountType
        - accountNumber
        - bankName
      properties:
        accountType:
          type: string
          enum:
            - ZAR_ACCOUNT
        accountNumber:
          type: string
          description: South African bank account number
          minLength: 9
          maxLength: 13
          example: '1234567890'
          pattern: ^[0-9]{9,13}$
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Standard Bank (South Africa)
      example:
        accountType: ZAR_ACCOUNT
        accountNumber: '1234567890'
        bankName: Standard Bank (South Africa)
    ZarAccountInfo:
      allOf:
        - $ref: '#/components/schemas/ZarAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    ZarBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    ZarBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    ZarExternalAccountInfo:
      title: ZAR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/ZarAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/ZarBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/ZarBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/ZarBeneficiary'
                  BUSINESS: '#/components/schemas/ZarBusinessBeneficiary'
    ZmwAccountInfoBase:
      type: object
      required:
        - accountType
        - phoneNumber
        - provider
      properties:
        accountType:
          type: string
          enum:
            - ZMW_ACCOUNT
        phoneNumber:
          type: string
          description: Zambian mobile money phone number
          example: '+260971234567'
          minLength: 7
          maxLength: 15
          pattern: ^\+260[0-9]{9}$
        provider:
          type: string
          description: The mobile money provider name
          minLength: 1
          maxLength: 255
      example:
        accountType: ZMW_ACCOUNT
        phoneNumber: '+260971234567'
        provider: Example Provider
    ZmwAccountInfo:
      allOf:
        - $ref: '#/components/schemas/ZmwAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - MOBILE_MONEY
    ZmwBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    ZmwBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - legalName
        - registrationNumber
        - taxId
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    ZmwExternalAccountInfo:
      title: ZMW Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/ZmwAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/ZmwBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/ZmwBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/ZmwBeneficiary'
                  BUSINESS: '#/components/schemas/ZmwBusinessBeneficiary'
    SwiftBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    SwiftExternalAccountInfo:
      title: SWIFT Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/SwiftAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/SwiftBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/SwiftBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    WalletIndividualBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
        - countryOfResidence
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
          example: John Michael Doe
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary (ISO 3166-1 alpha-2)
          example: US
    WalletBeneficiaryOneOf:
      description: The person or business that owns a crypto wallet external account.
      oneOf:
        - title: Individual Beneficiary
          $ref: '#/components/schemas/WalletIndividualBeneficiary'
        - title: Business Beneficiary
          $ref: '#/components/schemas/BusinessBeneficiary'
      discriminator:
        propertyName: beneficiaryType
        mapping:
          INDIVIDUAL: '#/components/schemas/WalletIndividualBeneficiary'
          BUSINESS: '#/components/schemas/BusinessBeneficiary'
    WalletBeneficiaryFields:
      type: object
      properties:
        beneficiary:
          description: The wallet's owner. Optional for `FIRST_PARTY` accounts — the customer's verified identity is used. Required for `THIRD_PARTY` wallets on platforms with counterparty requirements (e.g. EU Travel Rule); if missing there, creation fails with `400 INVALID_INPUT`.
          $ref: '#/components/schemas/WalletBeneficiaryOneOf'
    WalletCustodyFields:
      type: object
      properties:
        vaspName:
          type: string
          description: Name of the VASP that custodies this account. Present when the wallet is held at an exchange or other custodial platform; omit it for self-custody wallets. Ownership verification applies only to self-custody wallets.
          example: Kraken
    BaseWalletExternalAccountInfo:
      title: Base Wallet
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/BaseWalletInfo'
        - $ref: '#/components/schemas/WalletBeneficiaryFields'
        - $ref: '#/components/schemas/WalletCustodyFields'
    EthereumWalletExternalAccountInfo:
      title: Ethereum L1 Wallet
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/EthereumWalletInfo'
        - $ref: '#/components/schemas/WalletBeneficiaryFields'
        - $ref: '#/components/schemas/WalletCustodyFields'
    LightningInfo:
      type: object
      description: |
        Lightning payment destination. Exactly one of `invoice`, `bolt12`, or `lightningAddress` must be provided.
      required:
        - accountType
      properties:
        accountType:
          type: string
          enum:
            - LIGHTNING
        invoice:
          type: string
          description: 1-time use lightning bolt11 invoice payout destination
          example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs
        bolt12:
          type: string
          description: A bolt12 offer which can be reused as a payment destination
          example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs
        lightningAddress:
          type: string
          description: A lightning address which can be used as a payment destination. Note that for UMA addresses, no external account is needed. You can use the UMA address directly as a destination.
          example: john.doe@lightningwallet.com
    LightningExternalAccountInfo:
      title: Lightning
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/LightningInfo'
    PolygonWalletExternalAccountInfo:
      title: Polygon Wallet
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/PolygonWalletInfo'
        - $ref: '#/components/schemas/WalletBeneficiaryFields'
        - $ref: '#/components/schemas/WalletCustodyFields'
    PlasmaWalletExternalAccountInfo:
      title: Plasma Wallet
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/PlasmaWalletInfo'
        - $ref: '#/components/schemas/WalletBeneficiaryFields'
        - $ref: '#/components/schemas/WalletCustodyFields'
    ArbitrumWalletExternalAccountInfo:
      title: Arbitrum Wallet
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/ArbitrumWalletInfo'
        - $ref: '#/components/schemas/WalletBeneficiaryFields'
        - $ref: '#/components/schemas/WalletCustodyFields'
    SolanaWalletExternalAccountInfo:
      title: Solana Wallet
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/SolanaWalletInfo'
        - $ref: '#/components/schemas/WalletBeneficiaryFields'
        - $ref: '#/components/schemas/WalletCustodyFields'
    SparkWalletExternalAccountInfo:
      title: Spark Wallet
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/SparkWalletInfo'
        - $ref: '#/components/schemas/WalletBeneficiaryFields'
        - $ref: '#/components/schemas/WalletCustodyFields'
    TronWalletExternalAccountInfo:
      title: Tron Wallet
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/TronWalletInfo'
        - $ref: '#/components/schemas/WalletBeneficiaryFields'
        - $ref: '#/components/schemas/WalletCustodyFields'
    CnyAccountInfoBase:
      type: object
      required:
        - accountType
        - bankName
      description: |-
        Required fields depend on the selected paymentRails:
        - BANK_TRANSFER: accountNumber, bankName. Business-to-business only, so the beneficiary must be a business.
        - MOBILE_MONEY: bankName, phoneNumber. Pays an AliPay or WeChat Pay wallet; bankName selects the wallet.
      properties:
        accountType:
          type: string
          enum:
            - CNY_ACCOUNT
        accountNumber:
          type: string
          description: The account number of the bank
          minLength: 1
          maxLength: 34
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: China Construction Bank
        phoneNumber:
          type: string
          description: The phone number in international format
          example: '+1234567890'
          minLength: 7
          maxLength: 15
          pattern: ^\+[0-9]{6,14}$
      example:
        accountType: CNY_ACCOUNT
        accountNumber: '1234567890'
        bankName: China Construction Bank
        phoneNumber: '+1234567890'
    CnyAccountInfo:
      allOf:
        - $ref: '#/components/schemas/CnyAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
                  - MOBILE_MONEY
    CnyBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    CnyBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - address
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    CnyExternalAccountInfo:
      title: CNY Account
      description: A payout to a CNY bank account with a `BUSINESS` beneficiary needs supporting documents for its `purposeOfPayment`. Upload them with `POST /payment-documents` and pass their IDs in `documentIds` on `POST /quotes`. [Supporting documents](https://docs.lightspark.com/payouts-and-b2b/payment-flow/send-payment#supporting-documents) lists the documents each purpose needs.
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/CnyAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/CnyBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/CnyBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/CnyBeneficiary'
                  BUSINESS: '#/components/schemas/CnyBusinessBeneficiary'
    IlsAccountInfoBase:
      type: object
      required:
        - accountType
        - iban
        - bankName
      properties:
        accountType:
          type: string
          enum:
            - ILS_ACCOUNT
        iban:
          type: string
          description: Israeli IBAN (23 characters, starting with IL)
          example: IL620108000000099999999
          minLength: 23
          maxLength: 23
          pattern: ^IL[0-9]{21}$
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: Bank Leumi Le-Israel B.M
      example:
        accountType: ILS_ACCOUNT
        iban: IL620108000000099999999
        bankName: Bank Leumi Le-Israel B.M
    IlsAccountInfo:
      allOf:
        - $ref: '#/components/schemas/IlsAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    IlsBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    IlsBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - address
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    IlsExternalAccountInfo:
      title: ILS Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/IlsAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/IlsBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/IlsBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/IlsBeneficiary'
                  BUSINESS: '#/components/schemas/IlsBusinessBeneficiary'
    TryAccountInfoBase:
      type: object
      required:
        - accountType
        - iban
        - bankName
      properties:
        accountType:
          type: string
          enum:
            - TRY_ACCOUNT
        iban:
          type: string
          description: Turkish IBAN (26 characters, starting with TR)
          example: TR330006100519786457841326
          minLength: 26
          maxLength: 26
          pattern: ^TR[0-9]{2}[0-9A-Z]{22}$
        bankName:
          type: string
          description: The name of the bank. Must be one of the `bankName` values `GET /discoveries` returns for this country and currency. Send it exactly as returned; any other name is rejected.
          minLength: 1
          maxLength: 255
          example: <bankName from GET /discoveries>
      example:
        accountType: TRY_ACCOUNT
        iban: TR330006100519786457841326
        bankName: <bankName from GET /discoveries>
    TryAccountInfo:
      allOf:
        - $ref: '#/components/schemas/TryAccountInfoBase'
        - type: object
          required:
            - paymentRails
          properties:
            paymentRails:
              type: array
              items:
                type: string
                enum:
                  - BANK_TRANSFER
    TryBeneficiary:
      title: Individual Beneficiary
      type: object
      required:
        - beneficiaryType
        - countryOfResidence
        - fullName
      properties:
        beneficiaryType:
          type: string
          enum:
            - INDIVIDUAL
        fullName:
          type: string
          maxLength: 250
          description: The full name of the beneficiary
        birthDate:
          type: string
          description: The birth date of the beneficiary
        nationality:
          type: string
          description: The nationality of the beneficiary
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    TryBusinessBeneficiary:
      title: Business Beneficiary
      type: object
      required:
        - beneficiaryType
        - countryOfResidence
        - legalName
      properties:
        beneficiaryType:
          type: string
          enum:
            - BUSINESS
        legalName:
          type: string
          maxLength: 250
          description: The legal name of the business
        email:
          type: string
          maxLength: 320
          description: The email of the beneficiary
        phoneNumber:
          type: string
          maxLength: 50
          description: The phone number of the beneficiary
        registrationNumber:
          type: string
          maxLength: 100
          description: The company registration number of the business
        taxId:
          type: string
          maxLength: 100
          description: The tax identification number of the business
        countryOfResidence:
          type: string
          description: The country of residence of the beneficiary
        address:
          $ref: '#/components/schemas/Address'
    TryExternalAccountInfo:
      title: TRY Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/TryAccountInfo'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/TryBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/TryBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/TryBeneficiary'
                  BUSINESS: '#/components/schemas/TryBusinessBeneficiary'
    ExternalAccountInfoOneOf:
      oneOf:
        - $ref: '#/components/schemas/AedExternalAccountInfo'
        - $ref: '#/components/schemas/BdtExternalAccountInfo'
        - $ref: '#/components/schemas/BrlExternalAccountInfo'
        - $ref: '#/components/schemas/BwpExternalAccountInfo'
        - $ref: '#/components/schemas/CadExternalAccountInfo'
        - $ref: '#/components/schemas/CopExternalAccountInfo'
        - $ref: '#/components/schemas/DkkExternalAccountInfo'
        - $ref: '#/components/schemas/EgpExternalAccountInfo'
        - $ref: '#/components/schemas/EurExternalAccountInfo'
        - $ref: '#/components/schemas/GbpExternalAccountInfo'
        - $ref: '#/components/schemas/GhsExternalAccountInfo'
        - $ref: '#/components/schemas/GtqExternalAccountInfo'
        - $ref: '#/components/schemas/HkdExternalAccountInfo'
        - $ref: '#/components/schemas/HtgExternalAccountInfo'
        - $ref: '#/components/schemas/IdrExternalAccountInfo'
        - $ref: '#/components/schemas/InrExternalAccountInfo'
        - $ref: '#/components/schemas/JmdExternalAccountInfo'
        - $ref: '#/components/schemas/KesExternalAccountInfo'
        - $ref: '#/components/schemas/MwkExternalAccountInfo'
        - $ref: '#/components/schemas/MxnExternalAccountInfo'
        - $ref: '#/components/schemas/MyrExternalAccountInfo'
        - $ref: '#/components/schemas/NgnExternalAccountInfo'
        - $ref: '#/components/schemas/PhpExternalAccountInfo'
        - $ref: '#/components/schemas/PkrExternalAccountInfo'
        - $ref: '#/components/schemas/RwfExternalAccountInfo'
        - $ref: '#/components/schemas/SgdExternalAccountInfo'
        - $ref: '#/components/schemas/SlvExternalAccountInfo'
        - $ref: '#/components/schemas/ThbExternalAccountInfo'
        - $ref: '#/components/schemas/TzsExternalAccountInfo'
        - $ref: '#/components/schemas/UgxExternalAccountInfo'
        - $ref: '#/components/schemas/UsdExternalAccountInfo'
        - $ref: '#/components/schemas/VndExternalAccountInfo'
        - $ref: '#/components/schemas/XafExternalAccountInfo'
        - $ref: '#/components/schemas/XofExternalAccountInfo'
        - $ref: '#/components/schemas/ZarExternalAccountInfo'
        - $ref: '#/components/schemas/ZmwExternalAccountInfo'
        - $ref: '#/components/schemas/SwiftExternalAccountInfo'
        - $ref: '#/components/schemas/BaseWalletExternalAccountInfo'
        - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo'
        - $ref: '#/components/schemas/LightningExternalAccountInfo'
        - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo'
        - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo'
        - $ref: '#/components/schemas/ArbitrumWalletExternalAccountInfo'
        - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo'
        - $ref: '#/components/schemas/SparkWalletExternalAccountInfo'
        - $ref: '#/components/schemas/TronWalletExternalAccountInfo'
        - $ref: '#/components/schemas/CnyExternalAccountInfo'
        - $ref: '#/components/schemas/IlsExternalAccountInfo'
        - $ref: '#/components/schemas/TryExternalAccountInfo'
      discriminator:
        propertyName: accountType
        mapping:
          AED_ACCOUNT: '#/components/schemas/AedExternalAccountInfo'
          BDT_ACCOUNT: '#/components/schemas/BdtExternalAccountInfo'
          BRL_ACCOUNT: '#/components/schemas/BrlExternalAccountInfo'
          BWP_ACCOUNT: '#/components/schemas/BwpExternalAccountInfo'
          CAD_ACCOUNT: '#/components/schemas/CadExternalAccountInfo'
          COP_ACCOUNT: '#/components/schemas/CopExternalAccountInfo'
          DKK_ACCOUNT: '#/components/schemas/DkkExternalAccountInfo'
          EGP_ACCOUNT: '#/components/schemas/EgpExternalAccountInfo'
          EUR_ACCOUNT: '#/components/schemas/EurExternalAccountInfo'
          GBP_ACCOUNT: '#/components/schemas/GbpExternalAccountInfo'
          GHS_ACCOUNT: '#/components/schemas/GhsExternalAccountInfo'
          GTQ_ACCOUNT: '#/components/schemas/GtqExternalAccountInfo'
          HKD_ACCOUNT: '#/components/schemas/HkdExternalAccountInfo'
          HTG_ACCOUNT: '#/components/schemas/HtgExternalAccountInfo'
          IDR_ACCOUNT: '#/components/schemas/IdrExternalAccountInfo'
          INR_ACCOUNT: '#/components/schemas/InrExternalAccountInfo'
          JMD_ACCOUNT: '#/components/schemas/JmdExternalAccountInfo'
          KES_ACCOUNT: '#/components/schemas/KesExternalAccountInfo'
          MWK_ACCOUNT: '#/components/schemas/MwkExternalAccountInfo'
          MXN_ACCOUNT: '#/components/schemas/MxnExternalAccountInfo'
          MYR_ACCOUNT: '#/components/schemas/MyrExternalAccountInfo'
          NGN_ACCOUNT: '#/components/schemas/NgnExternalAccountInfo'
          PHP_ACCOUNT: '#/components/schemas/PhpExternalAccountInfo'
          PKR_ACCOUNT: '#/components/schemas/PkrExternalAccountInfo'
          RWF_ACCOUNT: '#/components/schemas/RwfExternalAccountInfo'
          SGD_ACCOUNT: '#/components/schemas/SgdExternalAccountInfo'
          SLV_ACCOUNT: '#/components/schemas/SlvExternalAccountInfo'
          THB_ACCOUNT: '#/components/schemas/ThbExternalAccountInfo'
          TZS_ACCOUNT: '#/components/schemas/TzsExternalAccountInfo'
          UGX_ACCOUNT: '#/components/schemas/UgxExternalAccountInfo'
          USD_ACCOUNT: '#/components/schemas/UsdExternalAccountInfo'
          VND_ACCOUNT: '#/components/schemas/VndExternalAccountInfo'
          XAF_ACCOUNT: '#/components/schemas/XafExternalAccountInfo'
          XOF_ACCOUNT: '#/components/schemas/XofExternalAccountInfo'
          ZAR_ACCOUNT: '#/components/schemas/ZarExternalAccountInfo'
          ZMW_ACCOUNT: '#/components/schemas/ZmwExternalAccountInfo'
          SWIFT_ACCOUNT: '#/components/schemas/SwiftExternalAccountInfo'
          BASE_WALLET: '#/components/schemas/BaseWalletExternalAccountInfo'
          ETHEREUM_WALLET: '#/components/schemas/EthereumWalletExternalAccountInfo'
          LIGHTNING: '#/components/schemas/LightningExternalAccountInfo'
          LIGHTNING_ACCOUNT: '#/components/schemas/LightningExternalAccountInfo'
          POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo'
          PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo'
          ARBITRUM_WALLET: '#/components/schemas/ArbitrumWalletExternalAccountInfo'
          SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo'
          SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo'
          TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo'
          CNY_ACCOUNT: '#/components/schemas/CnyExternalAccountInfo'
          ILS_ACCOUNT: '#/components/schemas/IlsExternalAccountInfo'
          TRY_ACCOUNT: '#/components/schemas/TryExternalAccountInfo'
    ExternalAccount:
      allOf:
        - type: object
          required:
            - id
            - status
            - currency
            - accountInfo
          properties:
            id:
              type: string
              description: The system generated identifier of this account
              example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
            customerId:
              type: string
              description: The customer this account is tied to, or null if the account is on behalf of the platform.
              example: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7
            status:
              $ref: '#/components/schemas/ExternalAccountStatus'
              description: Status of the external account
              example: ACTIVE
            platformAccountId:
              type: string
              description: Optional platform-specific identifier for this account
              example: acc_123456789
            ownershipType:
              $ref: '#/components/schemas/OwnershipType'
            currency:
              type: string
              description: The ISO 4217 currency code
              example: USD
            defaultUmaDepositAccount:
              type: boolean
              description: Whether this account is the default UMA deposit account for the customer. If true, incoming UMA payments to this customer's UMA address will be automatically deposited into this account instead of the primary internal account. False if not provided. Note that at most, one external account can be set as the default UMA deposit account for a customer. If there is no default UMA deposit account, incoming UMA payments will be deposited into the primary internal account for the customer.
              example: false
            beneficiaryVerificationStatus:
              $ref: '#/components/schemas/BeneficiaryVerificationStatus'
              description: The result of verifying the beneficiary name against the account holder name
            beneficiaryVerifiedData:
              $ref: '#/components/schemas/BeneficiaryVerifiedData'
              description: Verified beneficiary data returned by the payment rail, if available
            accountInfo:
              $ref: '#/components/schemas/ExternalAccountInfoOneOf'
    ExternalAccountListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of external accounts matching the filter criteria
          items:
            $ref: '#/components/schemas/ExternalAccount'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of external accounts matching the criteria (excluding pagination)
    AedExternalAccountCreateInfo:
      title: AED Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/AedAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/AedBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/AedBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/AedBeneficiary'
                  BUSINESS: '#/components/schemas/AedBusinessBeneficiary'
    BdtExternalAccountCreateInfo:
      title: BDT Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/BdtAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/BdtBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/BdtBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    BrlExternalAccountCreateInfo:
      title: BRL Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/BrlAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/BrlBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BrlBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/BrlBeneficiary'
                  BUSINESS: '#/components/schemas/BrlBusinessBeneficiary'
    BwpExternalAccountCreateInfo:
      title: BWP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/BwpAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/BwpBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BwpBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/BwpBeneficiary'
                  BUSINESS: '#/components/schemas/BwpBusinessBeneficiary'
    CadExternalAccountCreateInfo:
      title: CAD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/CadAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/CadBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/CadBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    CnyExternalAccountCreateInfo:
      title: CNY Account
      description: A payout to a CNY bank account with a `BUSINESS` beneficiary needs supporting documents for its `purposeOfPayment`. Upload them with `POST /payment-documents` and pass their IDs in `documentIds` on `POST /quotes`. [Supporting documents](https://docs.lightspark.com/payouts-and-b2b/payment-flow/send-payment#supporting-documents) lists the documents each purpose needs.
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/CnyAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/CnyBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/CnyBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/CnyBeneficiary'
                  BUSINESS: '#/components/schemas/CnyBusinessBeneficiary'
    CopExternalAccountCreateInfo:
      title: COP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/CopAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/CopBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/CopBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/CopBeneficiary'
                  BUSINESS: '#/components/schemas/CopBusinessBeneficiary'
    DkkExternalAccountCreateInfo:
      title: DKK Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/DkkAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/DkkBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/DkkBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/DkkBeneficiary'
                  BUSINESS: '#/components/schemas/DkkBusinessBeneficiary'
    EgpExternalAccountCreateInfo:
      title: EGP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/EgpAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/EgpBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/EgpBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/EgpBeneficiary'
                  BUSINESS: '#/components/schemas/EgpBusinessBeneficiary'
    EurExternalAccountCreateInfo:
      title: EUR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/EurAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/EurBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/EurBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/EurBeneficiary'
                  BUSINESS: '#/components/schemas/EurBusinessBeneficiary'
    GbpExternalAccountCreateInfo:
      title: GBP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/GbpAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/GbpBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/GbpBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/GbpBeneficiary'
                  BUSINESS: '#/components/schemas/GbpBusinessBeneficiary'
    GhsExternalAccountCreateInfo:
      title: GHS Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/GhsAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/GhsBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/GhsBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    GtqExternalAccountCreateInfo:
      title: GTQ Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/GtqAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/GtqBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/GtqBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    HkdExternalAccountCreateInfo:
      title: HKD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/HkdAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/HkdBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/HkdBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/HkdBeneficiary'
                  BUSINESS: '#/components/schemas/HkdBusinessBeneficiary'
    HtgExternalAccountCreateInfo:
      title: HTG Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/HtgAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/HtgBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/HtgBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    IdrExternalAccountCreateInfo:
      title: IDR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/IdrAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/IdrBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/IdrBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/IdrBeneficiary'
                  BUSINESS: '#/components/schemas/IdrBusinessBeneficiary'
    InrExternalAccountCreateInfo:
      title: INR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/InrAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/InrBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/InrBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/InrBeneficiary'
                  BUSINESS: '#/components/schemas/InrBusinessBeneficiary'
    JmdExternalAccountCreateInfo:
      title: JMD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/JmdAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/JmdBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/JmdBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    KesExternalAccountCreateInfo:
      title: KES Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/KesAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/KesBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/KesBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/KesBeneficiary'
                  BUSINESS: '#/components/schemas/KesBusinessBeneficiary'
    MwkExternalAccountCreateInfo:
      title: MWK Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/MwkAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/MwkBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/MwkBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/MwkBeneficiary'
                  BUSINESS: '#/components/schemas/MwkBusinessBeneficiary'
    MxnExternalAccountCreateInfo:
      title: MXN Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/MxnAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/MxnBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/MxnBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/MxnBeneficiary'
                  BUSINESS: '#/components/schemas/MxnBusinessBeneficiary'
    MyrExternalAccountCreateInfo:
      title: MYR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/MyrAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/MyrBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/MyrBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/MyrBeneficiary'
                  BUSINESS: '#/components/schemas/MyrBusinessBeneficiary'
    NgnExternalAccountCreateInfo:
      title: NGN Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/NgnAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/NgnBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/NgnBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/NgnBeneficiary'
                  BUSINESS: '#/components/schemas/NgnBusinessBeneficiary'
    PhpExternalAccountCreateInfo:
      title: PHP Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/PhpAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/PhpBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/PhpBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/PhpBeneficiary'
                  BUSINESS: '#/components/schemas/PhpBusinessBeneficiary'
    PkrExternalAccountCreateInfo:
      title: PKR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/PkrAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/PkrBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/PkrBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    RwfExternalAccountCreateInfo:
      title: RWF Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/RwfAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/RwfBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/RwfBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/RwfBeneficiary'
                  BUSINESS: '#/components/schemas/RwfBusinessBeneficiary'
    SgdExternalAccountCreateInfo:
      title: SGD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/SgdAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/SgdBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/SgdBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/SgdBeneficiary'
                  BUSINESS: '#/components/schemas/SgdBusinessBeneficiary'
    SlvExternalAccountCreateInfo:
      title: SLV Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/SlvAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/SlvBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/SlvBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    ThbExternalAccountCreateInfo:
      title: THB Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/ThbAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/ThbBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/ThbBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/ThbBeneficiary'
                  BUSINESS: '#/components/schemas/ThbBusinessBeneficiary'
    TzsExternalAccountCreateInfo:
      title: TZS Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/TzsAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/TzsBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/TzsBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/TzsBeneficiary'
                  BUSINESS: '#/components/schemas/TzsBusinessBeneficiary'
    UgxExternalAccountCreateInfo:
      title: UGX Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/UgxAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/UgxBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/UgxBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/UgxBeneficiary'
                  BUSINESS: '#/components/schemas/UgxBusinessBeneficiary'
    UsdExternalAccountCreateInfo:
      title: USD Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/UsdAccountInfoBase'
        - type: object
          required:
            - bankAccountType
            - beneficiary
          properties:
            bankAccountType:
              type: string
              description: Whether the account is a checking or a savings account. Grid uses this to set the ACH transaction code, so a value that does not match the account causes the receiving bank to return a notification of change.
              enum:
                - CHECKING
                - SAVINGS
              example: CHECKING
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/UsdBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/UsdBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/UsdBeneficiary'
                  BUSINESS: '#/components/schemas/UsdBusinessBeneficiary'
    VndExternalAccountCreateInfo:
      title: VND Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/VndAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/VndBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/VndBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/VndBeneficiary'
                  BUSINESS: '#/components/schemas/VndBusinessBeneficiary'
    XafExternalAccountCreateInfo:
      title: XAF Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/XafAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/XafBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/XafBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/XafBeneficiary'
                  BUSINESS: '#/components/schemas/XafBusinessBeneficiary'
    XofExternalAccountCreateInfo:
      title: XOF Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/XofAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/XofBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/XofBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/XofBeneficiary'
                  BUSINESS: '#/components/schemas/XofBusinessBeneficiary'
    ZarExternalAccountCreateInfo:
      title: ZAR Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/ZarAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/ZarBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/ZarBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/ZarBeneficiary'
                  BUSINESS: '#/components/schemas/ZarBusinessBeneficiary'
    ZmwExternalAccountCreateInfo:
      title: ZMW Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/ZmwAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/ZmwBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/ZmwBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/ZmwBeneficiary'
                  BUSINESS: '#/components/schemas/ZmwBusinessBeneficiary'
    SwiftExternalAccountCreateInfo:
      title: SWIFT Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/SwiftAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/SwiftBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/BusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/SwiftBeneficiary'
                  BUSINESS: '#/components/schemas/BusinessBeneficiary'
    IlsExternalAccountCreateInfo:
      title: ILS Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/IlsAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/IlsBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/IlsBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/IlsBeneficiary'
                  BUSINESS: '#/components/schemas/IlsBusinessBeneficiary'
    TryExternalAccountCreateInfo:
      title: TRY Account
      allOf:
        - $ref: '#/components/schemas/BaseExternalAccountInfo'
        - $ref: '#/components/schemas/TryAccountInfoBase'
        - type: object
          required:
            - beneficiary
          properties:
            beneficiary:
              oneOf:
                - title: Individual Beneficiary
                  $ref: '#/components/schemas/TryBeneficiary'
                - title: Business Beneficiary
                  $ref: '#/components/schemas/TryBusinessBeneficiary'
              discriminator:
                propertyName: beneficiaryType
                mapping:
                  INDIVIDUAL: '#/components/schemas/TryBeneficiary'
                  BUSINESS: '#/components/schemas/TryBusinessBeneficiary'
    ExternalAccountCreateInfoOneOf:
      oneOf:
        - $ref: '#/components/schemas/AedExternalAccountCreateInfo'
        - $ref: '#/components/schemas/BdtExternalAccountCreateInfo'
        - $ref: '#/components/schemas/BrlExternalAccountCreateInfo'
        - $ref: '#/components/schemas/BwpExternalAccountCreateInfo'
        - $ref: '#/components/schemas/CadExternalAccountCreateInfo'
        - $ref: '#/components/schemas/CnyExternalAccountCreateInfo'
        - $ref: '#/components/schemas/CopExternalAccountCreateInfo'
        - $ref: '#/components/schemas/DkkExternalAccountCreateInfo'
        - $ref: '#/components/schemas/EgpExternalAccountCreateInfo'
        - $ref: '#/components/schemas/EurExternalAccountCreateInfo'
        - $ref: '#/components/schemas/GbpExternalAccountCreateInfo'
        - $ref: '#/components/schemas/GhsExternalAccountCreateInfo'
        - $ref: '#/components/schemas/GtqExternalAccountCreateInfo'
        - $ref: '#/components/schemas/HkdExternalAccountCreateInfo'
        - $ref: '#/components/schemas/HtgExternalAccountCreateInfo'
        - $ref: '#/components/schemas/IdrExternalAccountCreateInfo'
        - $ref: '#/components/schemas/InrExternalAccountCreateInfo'
        - $ref: '#/components/schemas/JmdExternalAccountCreateInfo'
        - $ref: '#/components/schemas/KesExternalAccountCreateInfo'
        - $ref: '#/components/schemas/MwkExternalAccountCreateInfo'
        - $ref: '#/components/schemas/MxnExternalAccountCreateInfo'
        - $ref: '#/components/schemas/MyrExternalAccountCreateInfo'
        - $ref: '#/components/schemas/NgnExternalAccountCreateInfo'
        - $ref: '#/components/schemas/PhpExternalAccountCreateInfo'
        - $ref: '#/components/schemas/PkrExternalAccountCreateInfo'
        - $ref: '#/components/schemas/RwfExternalAccountCreateInfo'
        - $ref: '#/components/schemas/SgdExternalAccountCreateInfo'
        - $ref: '#/components/schemas/SlvExternalAccountCreateInfo'
        - $ref: '#/components/schemas/ThbExternalAccountCreateInfo'
        - $ref: '#/components/schemas/TzsExternalAccountCreateInfo'
        - $ref: '#/components/schemas/UgxExternalAccountCreateInfo'
        - $ref: '#/components/schemas/UsdExternalAccountCreateInfo'
        - $ref: '#/components/schemas/VndExternalAccountCreateInfo'
        - $ref: '#/components/schemas/XafExternalAccountCreateInfo'
        - $ref: '#/components/schemas/XofExternalAccountCreateInfo'
        - $ref: '#/components/schemas/ZarExternalAccountCreateInfo'
        - $ref: '#/components/schemas/ZmwExternalAccountCreateInfo'
        - $ref: '#/components/schemas/SwiftExternalAccountCreateInfo'
        - $ref: '#/components/schemas/BaseWalletExternalAccountInfo'
        - $ref: '#/components/schemas/EthereumWalletExternalAccountInfo'
        - $ref: '#/components/schemas/LightningExternalAccountInfo'
        - $ref: '#/components/schemas/PolygonWalletExternalAccountInfo'
        - $ref: '#/components/schemas/PlasmaWalletExternalAccountInfo'
        - $ref: '#/components/schemas/ArbitrumWalletExternalAccountInfo'
        - $ref: '#/components/schemas/SolanaWalletExternalAccountInfo'
        - $ref: '#/components/schemas/SparkWalletExternalAccountInfo'
        - $ref: '#/components/schemas/TronWalletExternalAccountInfo'
        - $ref: '#/components/schemas/IlsExternalAccountCreateInfo'
        - $ref: '#/components/schemas/TryExternalAccountCreateInfo'
      discriminator:
        propertyName: accountType
        mapping:
          AED_ACCOUNT: '#/components/schemas/AedExternalAccountCreateInfo'
          BDT_ACCOUNT: '#/components/schemas/BdtExternalAccountCreateInfo'
          BRL_ACCOUNT: '#/components/schemas/BrlExternalAccountCreateInfo'
          BWP_ACCOUNT: '#/components/schemas/BwpExternalAccountCreateInfo'
          CAD_ACCOUNT: '#/components/schemas/CadExternalAccountCreateInfo'
          CNY_ACCOUNT: '#/components/schemas/CnyExternalAccountCreateInfo'
          COP_ACCOUNT: '#/components/schemas/CopExternalAccountCreateInfo'
          DKK_ACCOUNT: '#/components/schemas/DkkExternalAccountCreateInfo'
          EGP_ACCOUNT: '#/components/schemas/EgpExternalAccountCreateInfo'
          EUR_ACCOUNT: '#/components/schemas/EurExternalAccountCreateInfo'
          GBP_ACCOUNT: '#/components/schemas/GbpExternalAccountCreateInfo'
          GHS_ACCOUNT: '#/components/schemas/GhsExternalAccountCreateInfo'
          GTQ_ACCOUNT: '#/components/schemas/GtqExternalAccountCreateInfo'
          HKD_ACCOUNT: '#/components/schemas/HkdExternalAccountCreateInfo'
          HTG_ACCOUNT: '#/components/schemas/HtgExternalAccountCreateInfo'
          IDR_ACCOUNT: '#/components/schemas/IdrExternalAccountCreateInfo'
          INR_ACCOUNT: '#/components/schemas/InrExternalAccountCreateInfo'
          JMD_ACCOUNT: '#/components/schemas/JmdExternalAccountCreateInfo'
          KES_ACCOUNT: '#/components/schemas/KesExternalAccountCreateInfo'
          MWK_ACCOUNT: '#/components/schemas/MwkExternalAccountCreateInfo'
          MXN_ACCOUNT: '#/components/schemas/MxnExternalAccountCreateInfo'
          MYR_ACCOUNT: '#/components/schemas/MyrExternalAccountCreateInfo'
          NGN_ACCOUNT: '#/components/schemas/NgnExternalAccountCreateInfo'
          PHP_ACCOUNT: '#/components/schemas/PhpExternalAccountCreateInfo'
          PKR_ACCOUNT: '#/components/schemas/PkrExternalAccountCreateInfo'
          RWF_ACCOUNT: '#/components/schemas/RwfExternalAccountCreateInfo'
          SGD_ACCOUNT: '#/components/schemas/SgdExternalAccountCreateInfo'
          SLV_ACCOUNT: '#/components/schemas/SlvExternalAccountCreateInfo'
          THB_ACCOUNT: '#/components/schemas/ThbExternalAccountCreateInfo'
          TZS_ACCOUNT: '#/components/schemas/TzsExternalAccountCreateInfo'
          UGX_ACCOUNT: '#/components/schemas/UgxExternalAccountCreateInfo'
          USD_ACCOUNT: '#/components/schemas/UsdExternalAccountCreateInfo'
          VND_ACCOUNT: '#/components/schemas/VndExternalAccountCreateInfo'
          XAF_ACCOUNT: '#/components/schemas/XafExternalAccountCreateInfo'
          XOF_ACCOUNT: '#/components/schemas/XofExternalAccountCreateInfo'
          ZAR_ACCOUNT: '#/components/schemas/ZarExternalAccountCreateInfo'
          ZMW_ACCOUNT: '#/components/schemas/ZmwExternalAccountCreateInfo'
          SWIFT_ACCOUNT: '#/components/schemas/SwiftExternalAccountCreateInfo'
          BASE_WALLET: '#/components/schemas/BaseWalletExternalAccountInfo'
          ETHEREUM_WALLET: '#/components/schemas/EthereumWalletExternalAccountInfo'
          LIGHTNING: '#/components/schemas/LightningExternalAccountInfo'
          POLYGON_WALLET: '#/components/schemas/PolygonWalletExternalAccountInfo'
          PLASMA_WALLET: '#/components/schemas/PlasmaWalletExternalAccountInfo'
          ARBITRUM_WALLET: '#/components/schemas/ArbitrumWalletExternalAccountInfo'
          SOLANA_WALLET: '#/components/schemas/SolanaWalletExternalAccountInfo'
          SPARK_WALLET: '#/components/schemas/SparkWalletExternalAccountInfo'
          TRON_WALLET: '#/components/schemas/TronWalletExternalAccountInfo'
          ILS_ACCOUNT: '#/components/schemas/IlsExternalAccountCreateInfo'
          TRY_ACCOUNT: '#/components/schemas/TryExternalAccountCreateInfo'
    CustomerExternalAccountCreateRequest:
      type: object
      required:
        - customerId
        - currency
        - accountInfo
      properties:
        customerId:
          type: string
          description: The ID of the customer who will own the external account. To create an external account owned by the platform itself, use `POST /platform/external-accounts` instead.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        currency:
          type: string
          description: The ISO 4217 currency code
          example: USD
        platformAccountId:
          type: string
          maxLength: 255
          description: Your platform's identifier for the account in your system. This can be used to reference the account by your own identifier.
          example: ext_acc_123456
        ownershipType:
          $ref: '#/components/schemas/OwnershipType'
        defaultUmaDepositAccount:
          type: boolean
          description: Whether to set the external account as the default UMA deposit account. When set to true, incoming payments to this customer's UMA address will be automatically deposited into this external account. False if not provided. Note that only one external account can be set as the default UMA deposit account for a customer, so if there is already a default UMA deposit account, this will override the existing default UMA deposit account. If there is no default UMA deposit account, incoming UMA payments will be deposited into the primary internal account for the customer.
          default: false
        accountInfo:
          $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf'
    OwnershipVerificationMethod:
      type: string
      enum:
        - WALLET_SIGNATURE
        - LIVENESS
      description: |
        The method used to verify ownership of a self-custody crypto wallet.

        | Method | Description |
        |--------|-------------|
        | `WALLET_SIGNATURE` | Prove control of the wallet by signing a message with the wallet's key |
        | `LIVENESS` | Prove identity via a hosted biometric verification flow |
      example: WALLET_SIGNATURE
    OwnershipChallengeRequest:
      type: object
      description: Starts (or restarts) an ownership verification challenge for a crypto wallet external account.
      required:
        - method
      properties:
        method:
          $ref: '#/components/schemas/OwnershipVerificationMethod'
          description: The verification method to use for this challenge.
    WalletSignatureChallenge:
      title: Wallet Signature Challenge
      type: object
      description: A challenge to prove ownership of the wallet by signing a message with the wallet's key.
      required:
        - method
        - messageToSign
        - expiresAt
      properties:
        method:
          type: string
          enum:
            - WALLET_SIGNATURE
          description: The verification method. Always `WALLET_SIGNATURE` for this shape.
          example: WALLET_SIGNATURE
        messageToSign:
          type: string
          description: The exact message the wallet must sign, character-for-character. Submit the resulting signature via the verify endpoint.
          example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02'
        expiresAt:
          type: string
          format: date-time
          description: When this challenge expires. Prompt the user promptly; after expiry, start a new challenge.
          example: '2025-08-15T15:32:00Z'
    LivenessChallenge:
      title: Liveness Challenge
      type: object
      description: A challenge to prove ownership through a hosted biometric verification flow. Completes asynchronously — the outcome is delivered via `EXTERNAL_ACCOUNT.STATUS_UPDATED` webhooks or by polling the external account.
      required:
        - method
        - verificationLink
        - expiresAt
      properties:
        method:
          type: string
          enum:
            - LIVENESS
          description: The verification method. Always `LIVENESS` for this shape.
          example: LIVENESS
        verificationLink:
          type: string
          format: uri
          description: Hosted verification URL to present to the user.
          example: https://verify.example.com/session/019542f5-b3e7-1d02
        token:
          type: string
          description: Provider-specific token that can be used in place of `verificationLink` — for example, to embed the provider's SDK directly in your application. Only returned for providers that support direct SDK integration. Whether to use the hosted URL or the embedded SDK is up to you; both flows result in the same verification outcome.
          example: eyJhbGciOiJIUzI1NiJ9.example
        expiresAt:
          type: string
          format: date-time
          description: When this challenge expires. Prompt the user promptly; after expiry, start a new challenge.
          example: '2025-08-15T15:32:00Z'
    OwnershipChallenge:
      description: An ownership verification challenge for a crypto wallet external account. The shape is determined by the challenge `method`.
      oneOf:
        - $ref: '#/components/schemas/WalletSignatureChallenge'
        - $ref: '#/components/schemas/LivenessChallenge'
      discriminator:
        propertyName: method
        mapping:
          WALLET_SIGNATURE: '#/components/schemas/WalletSignatureChallenge'
          LIVENESS: '#/components/schemas/LivenessChallenge'
    OwnershipVerifyRequest:
      type: object
      description: Completes a `WALLET_SIGNATURE` challenge by submitting the signature the wallet produced for the challenge's `messageToSign`.
      required:
        - signature
      properties:
        signature:
          type: string
          description: The signature produced over the exact `messageToSign` — EIP-191 hex for EVM chains, base64 for Bitcoin, base58-encoded Ed25519 for Solana.
          example: '0x52d75f01c9e7b8b2ce2fbcbd21bfeeee7bcd1a2f01ce6b8ad9a67a45e83a8f5d1c'
        signatureScheme:
          type: string
          enum:
            - bip137
            - electrum
          default: bip137
          description: Bitcoin message-signing format. Defaults to `bip137`; use `electrum` for Electrum/Sparrow wallets. Ignored for non-Bitcoin chains.
    PlatformExternalAccountCreateRequest:
      type: object
      required:
        - currency
        - accountInfo
      properties:
        currency:
          type: string
          description: The ISO 4217 currency code
          example: USD
        platformAccountId:
          type: string
          maxLength: 255
          description: Your platform's identifier for the account in your system. This can be used to reference the account by your own identifier.
          example: ext_acc_123456
        ownershipType:
          $ref: '#/components/schemas/OwnershipType'
        accountInfo:
          $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf'
    BeneficialOwnerListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of beneficial owners matching the filter criteria
          items:
            $ref: '#/components/schemas/BeneficialOwner'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of results matching the criteria
    BeneficialOwnerCreateRequest:
      type: object
      required:
        - customerId
        - roles
        - ownershipPercentage
        - personalInfo
      properties:
        customerId:
          type: string
          description: The ID of the business customer this beneficial owner is associated with
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        roles:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/BeneficialOwnerRole'
          description: Roles of this person within the business
          example:
            - UBO
            - DIRECTOR
        ownershipPercentage:
          type: integer
          description: Percentage of ownership in the business (0-100). Relevant when role includes UBO.
          minimum: 0
          maximum: 100
          example: 51
        personalInfo:
          $ref: '#/components/schemas/BeneficialOwnerPersonalInfo'
    BeneficialOwnerPersonalInfoUpdate:
      type: object
      description: Partial update for beneficial owner personal information. Only provided fields are updated.
      properties:
        firstName:
          type: string
          maxLength: 250
          description: First name of the individual
          example: Jane
        middleName:
          type: string
          maxLength: 250
          description: Middle name of the individual
          example: Marie
        lastName:
          type: string
          maxLength: 250
          description: Last name of the individual
          example: Smith
        birthDate:
          type: string
          format: date
          description: Date of birth in ISO 8601 format (YYYY-MM-DD)
          example: '1978-06-15'
        nationality:
          type: string
          description: Country of nationality (ISO 3166-1 alpha-2)
          example: US
        email:
          type: string
          format: email
          maxLength: 255
          description: Email address of the individual
          example: jane.smith@acmecorp.com
        phoneNumber:
          type: string
          description: Phone number in E.164 format
          example: '+14155550192'
          pattern: ^\+[1-9]\d{1,14}$
        address:
          $ref: '#/components/schemas/Address'
        idType:
          $ref: '#/components/schemas/IdentificationType'
        identifier:
          type: string
          maxLength: 100
          description: The identification number or value
          example: 123-45-6789
        countryOfIssuance:
          type: string
          description: Country that issued the identification (ISO 3166-1 alpha-2)
          example: US
    BeneficialOwnerUpdateRequest:
      type: object
      properties:
        roles:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/BeneficialOwnerRole'
          description: Roles of this person within the business
          example:
            - UBO
            - DIRECTOR
        ownershipPercentage:
          type: integer
          description: Percentage of ownership in the business (0-100)
          minimum: 0
          maximum: 100
          example: 51
        personalInfo:
          $ref: '#/components/schemas/BeneficialOwnerPersonalInfoUpdate'
    DocumentType:
      type: string
      enum:
        - PASSPORT
        - DRIVERS_LICENSE
        - NATIONAL_ID
        - PROOF_OF_ADDRESS
        - BANK_STATEMENT
        - TAX_RETURN
        - CERTIFICATE_OF_INCORPORATION
        - ARTICLES_OF_INCORPORATION
        - ARTICLES_OF_ASSOCIATION
        - STATE_REGISTRY_EXCERPT
        - GOOD_STANDING_CERTIFICATE
        - INFORMATION_STATEMENT
        - INCUMBENCY_CERTIFICATE
        - BUSINESS_LICENSE
        - SHAREHOLDER_REGISTER
        - POWER_OF_ATTORNEY
        - UTILITY_BILL
        - ELECTRICITY_BILL
        - RENT_OR_LEASE_AGREEMENT
        - DIRECTOR_REGISTRY
        - TRUST_AGREEMENT
        - STATE_COMPANY_REGISTRY
        - PARTNERSHIP_CONTROL_AGREEMENT
        - PARTNERSHIP_AGREEMENT
        - SELFIE
        - OTHER
      description: |-
        Type of identity or business verification document. Document types are grouped by verification category:
        **Identity** — PASSPORT, DRIVERS_LICENSE, NATIONAL_ID
        **Business — Legal presence** — CERTIFICATE_OF_INCORPORATION, ARTICLES_OF_INCORPORATION, ARTICLES_OF_ASSOCIATION, STATE_REGISTRY_EXCERPT
        **Business — Control structure** — DIRECTOR_REGISTRY, TRUST_AGREEMENT, STATE_COMPANY_REGISTRY, PARTNERSHIP_CONTROL_AGREEMENT
        **Business — Ownership structure** — SHAREHOLDER_REGISTER, TRUST_AGREEMENT, PARTNERSHIP_AGREEMENT
        **Proof of address** — UTILITY_BILL, RENT_OR_LEASE_AGREEMENT, ELECTRICITY_BILL, BANK_STATEMENT, TAX_RETURN
      example: PASSPORT
    Document:
      type: object
      required:
        - id
        - documentHolder
        - documentType
        - country
        - fileName
        - createdAt
      properties:
        id:
          type: string
          description: Unique identifier for this document
          example: Document:019542f5-b3e7-1d02-0000-000000000001
        documentHolder:
          type: string
          description: ID of the entity that owns this document. Can be a Customer ID or a BeneficialOwner ID.
          example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001
        documentType:
          $ref: '#/components/schemas/DocumentType'
        side:
          type: string
          enum:
            - FRONT
            - BACK
          description: Which side of the document this upload represents. Relevant for two-sided documents like driver's licenses or national IDs.
          example: FRONT
        country:
          type: string
          description: Country that issued the document (ISO 3166-1 alpha-2)
          example: US
        documentNumber:
          type: string
          description: Document identification number (e.g., passport number)
          example: A12345678
        issuingAuthority:
          type: string
          description: Name of the government agency or organization that issued the document
          example: U.S. Department of State
        fileName:
          type: string
          description: Original file name of the uploaded document
          example: passport_scan.pdf
        createdAt:
          type: string
          format: date-time
          description: When this document was uploaded
          example: '2025-10-03T12:00:00Z'
        updatedAt:
          type: string
          format: date-time
          description: When this document was last updated
          example: '2025-10-03T12:00:00Z'
    DocumentListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of documents matching the filter criteria
          items:
            $ref: '#/components/schemas/Document'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of results matching the criteria
    BaseDocumentRequest:
      type: object
      required:
        - file
        - documentType
        - country
      properties:
        file:
          type: string
          format: binary
          description: |
            The document file. Grid accepts three formats, matched on the
            `Content-Type` of the multipart part rather than the file extension:
            `application/pdf`, `image/jpeg`, and `image/png`. Any other type,
            including `image/heic`, `image/webp`, and `image/tiff`, returns
            `400 INVALID_INPUT`. The file must be 10 MB or smaller.
        documentType:
          $ref: '#/components/schemas/DocumentType'
        side:
          type: string
          enum:
            - FRONT
            - BACK
          description: |
            Which side of the document this image shows. Required when
            `documentType` is `DRIVERS_LICENSE` or `NATIONAL_ID`: upload the
            `FRONT` and `BACK` as two separate documents. A `PASSPORT`, or a
            `DRIVERS_LICENSE` issued in Brazil (`country` is `BR`), is a single
            image: sending `side` for either returns `400 INVALID_INPUT`. Optional
            for all other document types.
          example: FRONT
        country:
          type: string
          description: Country that issued the document (ISO 3166-1 alpha-2)
          example: US
        documentNumber:
          type: string
          maxLength: 100
          description: |
            Identification number printed on the document, such as a passport
            number. Required when `documentType` is `PASSPORT`,
            `DRIVERS_LICENSE`, or `NATIONAL_ID`.
          example: A12345678
        issuingAuthority:
          type: string
          maxLength: 255
          description: |
            Name of the government agency or organization that issued the
            document. Required when `documentType` is `PASSPORT`,
            `DRIVERS_LICENSE`, or `NATIONAL_ID`.
          example: U.S. Department of State
    DocumentUploadRequest:
      title: Document Upload Request
      allOf:
        - $ref: '#/components/schemas/BaseDocumentRequest'
        - type: object
          required:
            - documentHolder
          properties:
            documentHolder:
              type: string
              description: ID of the entity that owns this document. Can be a Customer ID or a BeneficialOwner ID.
              example: BeneficialOwner:019542f5-b3e7-1d02-0000-000000000001
    Error422:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | DOCUMENT_REJECTED | The verification provider screened the uploaded file and marked it unusable. `precheckErrors` names each problem it found |
          enum:
            - DOCUMENT_REJECTED
        reason:
          type: string
          description: Error message
        precheckErrors:
          type: array
          description: One entry per problem the verification provider found in the uploaded file, passed through verbatim. Common values are `dataNotReadable` (the data on the document cannot be read), `screenshot` (the file is a screen capture rather than a photo or scan), `grayscale` (a black-and-white copy of a color document), and `documentExpired`.
          items:
            type: string
          example:
            - dataNotReadable
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    DocumentReplaceRequest:
      title: Document Replace Request
      allOf:
        - $ref: '#/components/schemas/BaseDocumentRequest'
    VerificationStatus:
      type: string
      enum:
        - RESOLVE_ERRORS
        - PENDING_MANUAL_REVIEW
        - IN_PROGRESS
        - APPROVED
        - REJECTED
        - READY_FOR_VERIFICATION
      description: Current status of the KYC/KYB verification
      example: RESOLVE_ERRORS
    VerificationErrorType:
      type: string
      enum:
        - MISSING_FIELD
        - INVALID_FIELD
        - MISSING_LEGAL_PRESENCE_DOCUMENT
        - MISSING_CONTROL_STRUCTURE_DOCUMENT
        - MISSING_OWNERSHIP_STRUCTURE_DOCUMENT
        - MISSING_PROOF_OF_ADDRESS_DOCUMENT
        - MISSING_IDENTITY_DOCUMENT
        - INVALID_DOCUMENT
        - EXPIRED_DOCUMENT
        - POOR_QUALITY_DOCUMENT
        - SUSPECTED_FRAUD_DOCUMENT
        - WRONG_DOCUMENT_TYPE
        - INCOMPLETE_DOCUMENT
        - UNREADABLE_DOCUMENT
        - DOCUMENT_VERIFICATION_FAILED
        - APPLICANT_SANCTIONED
        - APPLICANT_FRAUD
        - APPLICANT_CRIMINAL_RECORD
        - APPLICANT_REJECTED
        - MISSING_BENEFICIAL_OWNER
        - MISSING_CONTROL_PERSON
        - MISSING_GOOD_STANDING_DOCUMENT
      description: Type of verification error. The category-specific MISSING_*_DOCUMENT types indicate which document category is needed. Document quality types (POOR_QUALITY_DOCUMENT, SUSPECTED_FRAUD_DOCUMENT, etc.) indicate specific issues with uploaded documents. APPLICANT_* types indicate issues with the applicant themselves (sanctions, fraud, criminal records).
      example: MISSING_FIELD
    VerificationError:
      type: object
      required:
        - resourceId
        - type
        - reason
      properties:
        resourceId:
          type: string
          description: ID of the resource with the issue (Customer, BeneficialOwner, or Document)
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        type:
          $ref: '#/components/schemas/VerificationErrorType'
        field:
          type: string
          description: Dot-notation path to the field with the issue. Present when type is MISSING_FIELD or INVALID_FIELD.
          example: customer.address.line1
        acceptedDocumentTypes:
          type: array
          items:
            $ref: '#/components/schemas/DocumentType'
          description: |-
            Document types that would satisfy this requirement. The integrator can upload any one of the listed types. Present when type is MISSING_LEGAL_PRESENCE_DOCUMENT, MISSING_CONTROL_STRUCTURE_DOCUMENT, MISSING_OWNERSHIP_STRUCTURE_DOCUMENT, MISSING_PROOF_OF_ADDRESS_DOCUMENT, MISSING_IDENTITY_DOCUMENT, INVALID_DOCUMENT, or EXPIRED_DOCUMENT.

            | Error Type | Accepted Document Types |
            |---|---|
            | MISSING_LEGAL_PRESENCE_DOCUMENT | CERTIFICATE_OF_INCORPORATION, ARTICLES_OF_INCORPORATION, ARTICLES_OF_ASSOCIATION, STATE_REGISTRY_EXCERPT |
            | MISSING_CONTROL_STRUCTURE_DOCUMENT | DIRECTOR_REGISTRY, TRUST_AGREEMENT, STATE_COMPANY_REGISTRY, PARTNERSHIP_CONTROL_AGREEMENT |
            | MISSING_OWNERSHIP_STRUCTURE_DOCUMENT | SHAREHOLDER_REGISTER, TRUST_AGREEMENT, PARTNERSHIP_AGREEMENT |
            | MISSING_PROOF_OF_ADDRESS_DOCUMENT | UTILITY_BILL, RENT_OR_LEASE_AGREEMENT, ELECTRICITY_BILL, BANK_STATEMENT, TAX_RETURN |
            | MISSING_IDENTITY_DOCUMENT | PASSPORT, DRIVERS_LICENSE, NATIONAL_ID |
        reason:
          type: string
          description: Human-readable description of the issue
          example: Business address line 1 is required
    Verification:
      type: object
      required:
        - id
        - customerId
        - verificationStatus
        - errors
        - createdAt
      properties:
        id:
          type: string
          description: Unique identifier for this verification
          example: Verification:019542f5-b3e7-1d02-0000-000000000001
        customerId:
          type: string
          description: The ID of the customer being verified
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        verificationStatus:
          $ref: '#/components/schemas/VerificationStatus'
        errors:
          type: array
          description: List of issues preventing verification from proceeding. Empty when verificationStatus is APPROVED or IN_PROGRESS.
          items:
            $ref: '#/components/schemas/VerificationError'
        createdAt:
          type: string
          format: date-time
          description: When this verification was created
          example: '2025-10-03T12:00:00Z'
        updatedAt:
          type: string
          format: date-time
          description: When this verification was last updated
          example: '2025-10-03T12:00:00Z'
    VerificationListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of verifications matching the filter criteria
          items:
            $ref: '#/components/schemas/Verification'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of results matching the criteria
    VerificationRequest:
      type: object
      required:
        - customerId
      properties:
        customerId:
          type: string
          description: The ID of the customer to verify
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
    ExternalAccountReference:
      type: object
      required:
        - accountId
      properties:
        accountId:
          type: string
          description: Reference to an external account ID
          example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
    InternalAccountReference:
      type: object
      required:
        - accountId
      properties:
        accountId:
          type: string
          description: Reference to an internal account ID
          example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
    TransferInRequest:
      type: object
      required:
        - source
        - destination
      properties:
        source:
          $ref: '#/components/schemas/ExternalAccountReference'
          description: Source external account details
        destination:
          $ref: '#/components/schemas/InternalAccountReference'
          description: Destination internal account details
        amount:
          type: integer
          format: int64
          description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC)
          example: 12550
    TransactionStatus:
      type: string
      enum:
        - CREATED
        - PENDING
        - PENDING_AUTHORIZATION
        - PROCESSING
        - COMPLETED
        - REJECTED
        - FAILED
        - REFUNDED
        - EXPIRED
      description: |
        Status of a payment transaction.

        | Status | Description |
        |--------|-------------|
        | `CREATED` | Initial lookup has been created |
        | `PENDING` | Quote has been created |
        | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU). The challenge is carried by the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` via `POST /quotes/{quoteId}/authorize`. |
        | `PROCESSING` | Funding has been received and payment initiated |
        | `COMPLETED` | The funds are where settlement put them. A return that takes them back moves the transaction to `FAILED`, and the returned side's `railDetails` shows `RETURNED`. A transaction stays `COMPLETED` through a return only when a later step that can't be undone already used the funds |
        | `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded |
        | `FAILED` | The payment did not reach its destination, or the funds were sent back after it did. Not terminal for an incoming payment: a dishonored return restores the credit and moves it to `COMPLETED`. A returned payout stays `FAILED`, since it has already been refunded. Each step shows on the returned side's `railDetails` |
        | `REFUNDED` | Deprecated. A refund is reported on the transaction's `refund` object, and the status stays `FAILED` |
        | `EXPIRED` | Quote has expired. Terminal |
    TransactionPendingReason:
      type: string
      enum:
        - COUNTERPARTY_DECLARATION_REQUIRED
        - WALLET_VERIFICATION_REQUIRED
        - COUNTERPARTY_INFORMATION_REQUIRED
        - COMPLIANCE_REVIEW
      description: |
        Why a transaction is pending.

        | Reason | Description |
        |--------|-------------|
        | `COUNTERPARTY_DECLARATION_REQUIRED` | The customer must declare who controls the sending wallet. |
        | `WALLET_VERIFICATION_REQUIRED` | The customer must verify ownership of the sending wallet. |
        | `COUNTERPARTY_INFORMATION_REQUIRED` | Information about the sender is required. |
        | `COMPLIANCE_REVIEW` | The customer's action is complete and the deposit remains under compliance review. |
    TransactionDirection:
      type: string
      enum:
        - CREDIT
        - DEBIT
      description: 'Whether the transaction credits (funds in) or debits (funds out) the customer''s account. Independent of `type`: an incoming transaction is normally a `CREDIT`, but an inbound ACH pull, for example, is an `INCOMING` transaction with a `DEBIT` direction.'
    TransactionDestinationType:
      type: string
      enum:
        - ACCOUNT
        - UMA_ADDRESS
      description: Type of transaction destination
      example: ACCOUNT
    BaseTransactionDestination:
      type: object
      required:
        - destinationType
      properties:
        destinationType:
          $ref: '#/components/schemas/TransactionDestinationType'
        currency:
          type: string
          description: Currency code for the destination
          example: EUR
    CryptoNetwork:
      type: string
      enum:
        - BITCOIN
        - ETHEREUM
        - SOLANA
        - BASE
        - POLYGON
        - TRON
        - PLASMA
        - ARBITRUM
        - SPARK
      description: The blockchain network an on-chain transaction settled on. Whether this is the mainnet or a test network (e.g. Solana devnet) is determined by your platform's environment — sandbox platforms operate on test networks, production platforms on mainnet — mirroring how `cryptoNetwork` is interpreted elsewhere in the API.
      example: SOLANA
    OnChainRailDetails:
      title: On-Chain Transfer
      type: object
      required:
        - paymentRail
        - transactionHash
        - network
      properties:
        paymentRail:
          type: string
          enum:
            - ONCHAIN
        transactionHash:
          type: string
          description: On-chain transaction hash of the crypto transfer for this leg of the transaction.
          example: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx
        network:
          $ref: '#/components/schemas/CryptoNetwork'
          description: Blockchain network the transaction settled on (mainnet vs test network is determined by your platform environment).
      description: A crypto transfer to or from an external wallet.
    UsdBankAccountDetails:
      title: US Bank Account Details
      type: object
      description: 'The account on the other end of the transfer: the sender''s account on a source, the recipient''s account on a destination.'
      properties:
        accountHolderName:
          type: string
          description: Name of the account holder.
          example: John Sender
        routingNumber:
          type: string
          description: 9-digit ABA routing number of the account's bank.
          example: '021000021'
        accountNumber:
          type: string
          description: Account number, masked to its last four digits.
          example: '****6789'
    BaseUsdBankRailDetails:
      title: Base US Bank Rail Details
      type: object
      description: Fields shared by every US bank rail.
      properties:
        bankAccount:
          $ref: '#/components/schemas/UsdBankAccountDetails'
        settledAt:
          type: string
          format: date-time
          description: When the transfer settled.
          example: '2026-09-29T15:04:12Z'
    AchRailDetails:
      title: ACH Transfer
      description: A transfer over the ACH network.
      allOf:
        - $ref: '#/components/schemas/BaseUsdBankRailDetails'
        - type: object
          required:
            - paymentRail
            - status
          properties:
            paymentRail:
              type: string
              enum:
                - ACH
                - ACH_SAME_DAY
            status:
              type: string
              enum:
                - PENDING
                - SETTLED
                - RETURNED
                - RETURN_DISHONORED
                - RETURN_CONTESTED
                - REVERSED
                - FAILED
              description: |-
                Status of the entry on the ACH network.

                | Status | Description |
                |--------|-------------|
                | `PENDING` | Submitted to the ACH network and not yet settled. |
                | `SETTLED` | The entry settled. |
                | `RETURNED` | The receiving bank returned the entry, before or after it settled. |
                | `RETURN_DISHONORED` | The originating bank dishonored the return, so the returned funds came back. |
                | `RETURN_CONTESTED` | The receiving bank contested the dishonor, so the funds went back again. |
                | `REVERSED` | The originator reversed the entry after it settled. |
                | `FAILED` | The entry was rejected before submission and no funds moved. |

                A return, dishonor or contest can arrive days after the step before it. This status
                reports the ACH entry; the transaction's top-level `status` reports where the funds
                are now. A return moves the transaction to `FAILED`. On an incoming payment, a
                dishonor brings the credit back and moves it to `COMPLETED`, and a contest moves it
                to `FAILED` again. A returned payout stays `FAILED` through a dishonor, since it has
                already been refunded; the rail status still records the dishonor.
              example: SETTLED
            traceNumber:
              type: string
              description: 15-digit ACH trace number of the entry.
              example: '021000020123456'
            addenda:
              type: string
              description: Payment-related text carried in the entry's addenda record. On payments you originate, this is the `remittanceInformation` you provided.
              example: INV-12345
            returnedAt:
              type: string
              format: date-time
              description: When the receiving bank returned the entry. Present once the entry has been returned, including after a later dishonor or contest.
              example: '2026-10-01T14:00:00Z'
            dishonoredAt:
              type: string
              format: date-time
              description: When the originating bank dishonored the return. Present once `status` has reached `RETURN_DISHONORED`.
              example: '2026-10-04T14:00:00Z'
            contestedAt:
              type: string
              format: date-time
              description: When the receiving bank contested the dishonor. Present when `status` is `RETURN_CONTESTED`.
              example: '2026-10-07T14:00:00Z'
            reversedAt:
              type: string
              format: date-time
              description: When the originator reversed the entry. Present when `status` is `REVERSED`.
              example: '2026-10-01T14:00:00Z'
    WireBankAccountDetails:
      title: Wire Bank Account Details
      allOf:
        - $ref: '#/components/schemas/UsdBankAccountDetails'
        - type: object
          properties:
            bankName:
              type: string
              description: Name of the account's bank.
              example: Chase Bank
    WireRailDetails:
      title: Wire Transfer
      description: A domestic wire over Fedwire.
      allOf:
        - $ref: '#/components/schemas/BaseUsdBankRailDetails'
        - type: object
          required:
            - paymentRail
            - status
          properties:
            bankAccount:
              $ref: '#/components/schemas/WireBankAccountDetails'
            paymentRail:
              type: string
              enum:
                - WIRE
            status:
              type: string
              enum:
                - PENDING
                - SETTLED
                - FAILED
              description: |-
                Status of the wire on Fedwire.

                | Status | Description |
                |--------|-------------|
                | `PENDING` | Submitted to Fedwire and not yet delivered. |
                | `SETTLED` | Fedwire delivered the wire. |
                | `FAILED` | Fedwire or the receiving bank rejected the wire and no funds moved. |
              example: SETTLED
            imad:
              type: string
              description: Fedwire Input Message Accountability Data assigned when the wire was sent.
              example: 20260929MMQFMP9C000123
            originatorToBeneficiaryInformation:
              type: string
              description: Free-form originator-to-beneficiary information (OBI) sent with the wire.
              example: INV-12345
    FedNowRailDetails:
      title: FedNow Transfer
      description: An instant payment over the FedNow Service.
      allOf:
        - $ref: '#/components/schemas/BaseUsdBankRailDetails'
        - type: object
          required:
            - paymentRail
            - status
          properties:
            paymentRail:
              type: string
              enum:
                - FEDNOW
            status:
              type: string
              enum:
                - PENDING
                - SETTLED
                - FAILED
              description: |-
                Status of the payment on FedNow.

                | Status | Description |
                |--------|-------------|
                | `PENDING` | Submitted and awaiting the receiving bank's response. |
                | `SETTLED` | The receiving bank accepted the payment and the funds settled. |
                | `FAILED` | The receiving bank or the network rejected the payment and no funds moved. |
              example: SETTLED
            endToEndId:
              type: string
              description: The originator's end-to-end reference for the payment.
              example: E2E-9f2c6b6f
            remittanceInformation:
              type: string
              description: Unstructured remittance information sent with the payment.
              example: INV-12345
    RtpRailDetails:
      title: RTP Transfer
      description: An instant payment over The Clearing House RTP network.
      allOf:
        - $ref: '#/components/schemas/BaseUsdBankRailDetails'
        - type: object
          required:
            - paymentRail
            - status
          properties:
            paymentRail:
              type: string
              enum:
                - RTP
            status:
              type: string
              enum:
                - PENDING
                - SETTLED
                - FAILED
              description: |-
                Status of the payment on the RTP network.

                | Status | Description |
                |--------|-------------|
                | `PENDING` | Submitted and awaiting the receiving bank's response. |
                | `SETTLED` | The receiving bank accepted the payment and the funds settled. |
                | `FAILED` | The receiving bank or the network rejected the payment and no funds moved. |
              example: SETTLED
            endToEndId:
              type: string
              description: The originator's end-to-end reference for the payment.
              example: E2E-9f2c6b6f
            remittanceInformation:
              type: string
              description: Unstructured remittance information sent with the payment.
              example: INV-12345
    RailDetailsOneOf:
      description: 'The rail transfer that moved funds for this side of the transaction. `paymentRail` determines the shape: each rail carries its own identifiers. Absent until a rail transfer exists for this side, and on sides whose funds move within Grid.'
      oneOf:
        - $ref: '#/components/schemas/OnChainRailDetails'
        - $ref: '#/components/schemas/AchRailDetails'
        - $ref: '#/components/schemas/WireRailDetails'
        - $ref: '#/components/schemas/FedNowRailDetails'
        - $ref: '#/components/schemas/RtpRailDetails'
      discriminator:
        propertyName: paymentRail
        mapping:
          ONCHAIN: '#/components/schemas/OnChainRailDetails'
          ACH: '#/components/schemas/AchRailDetails'
          ACH_SAME_DAY: '#/components/schemas/AchRailDetails'
          WIRE: '#/components/schemas/WireRailDetails'
          FEDNOW: '#/components/schemas/FedNowRailDetails'
          RTP: '#/components/schemas/RtpRailDetails'
    OnChainTransaction:
      type: object
      required:
        - transactionHash
        - network
      properties:
        transactionHash:
          type: string
          description: On-chain transaction hash of the crypto transfer for this leg of the transaction.
          example: h82pJGF9p7kpzb6eU326EFZf2cDnimbTFVeJtx1qtBmUNJAEqN76R7PwPfHt3oWb8R6cKvhgyxQdDn53jFrK6wFx
        network:
          $ref: '#/components/schemas/CryptoNetwork'
          description: Blockchain network the transaction settled on (mainnet vs test network is determined by your platform environment).
    AccountTransactionDestination:
      title: Account Destination
      allOf:
        - $ref: '#/components/schemas/BaseTransactionDestination'
        - type: object
          required:
            - accountId
            - destinationType
          properties:
            destinationType:
              type: string
              enum:
                - ACCOUNT
            accountId:
              type: string
              description: Destination account identifier
              example: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
            railDetails:
              $ref: '#/components/schemas/RailDetailsOneOf'
            onChainTransaction:
              $ref: '#/components/schemas/OnChainTransaction'
              deprecated: true
              description: Deprecated. Use `railDetails`, which carries the same hash and network when `paymentRail` is `ONCHAIN`.
          description: Destination account details
    UmaAddressTransactionDestination:
      title: UMA Address Destination
      allOf:
        - $ref: '#/components/schemas/BaseTransactionDestination'
        - type: object
          required:
            - umaAddress
            - destinationType
          properties:
            destinationType:
              type: string
              enum:
                - UMA_ADDRESS
            umaAddress:
              type: string
              description: UMA address of the recipient
              example: $receiver@uma.domain.com
          description: UMA address destination details
    TransactionDestinationOneOf:
      oneOf:
        - $ref: '#/components/schemas/AccountTransactionDestination'
        - $ref: '#/components/schemas/UmaAddressTransactionDestination'
      discriminator:
        propertyName: destinationType
        mapping:
          ACCOUNT: '#/components/schemas/AccountTransactionDestination'
          UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionDestination'
    Refund:
      type: object
      required:
        - reference
        - initiatedAt
        - status
      properties:
        reference:
          type: string
          description: The unique reference ID of the refund
          example: UMA-Q12345-REFUND
        initiatedAt:
          type: string
          format: date-time
          description: When the refund was initiated
          example: '2025-08-15T14:30:00Z'
        settledAt:
          type: string
          format: date-time
          description: When the refund was settled
          example: '2025-08-15T14:35:00Z'
        status:
          type: string
          enum:
            - PENDING
            - COMPLETED
            - FAILED
          description: Current status of the refund
          example: COMPLETED
        reason:
          type: string
          enum:
            - TRANSACTION_FAILED
            - USER_CANCELLATION
            - TIMEOUT
          description: Reason for the refund
          example: TRANSACTION_FAILED
    CounterpartyInformation:
      type: object
      description: Additional information about the counterparty, if available and relevant to the transaction and platform.
      additionalProperties: true
      example:
        FULL_NAME: John Sender
        BIRTH_DATE: '1985-06-15'
        NATIONALITY: DE
    Transaction:
      type: object
      required:
        - id
        - status
        - type
        - direction
        - destination
        - customerId
        - platformCustomerId
      properties:
        id:
          type: string
          description: Unique identifier for the transaction
          example: Transaction:019542f5-b3e7-1d02-0000-000000000004
        status:
          $ref: '#/components/schemas/TransactionStatus'
        pendingReason:
          $ref: '#/components/schemas/TransactionPendingReason'
          description: Present when compliance review or required customer action is delaying settlement.
        type:
          $ref: '#/components/schemas/TransactionType'
        direction:
          $ref: '#/components/schemas/TransactionDirection'
          description: Whether this transaction credits or debits the customer's account.
        destination:
          $ref: '#/components/schemas/TransactionDestinationOneOf'
        ruleBasedAccountId:
          type: string
          description: The `RULE_BASED` internal account whose deposit this transaction sweeps. Present only on sweep transactions. For these, `source` describes the party that made the deposit when it is known, and is this account otherwise.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000011
        customerId:
          type: string
          description: System ID of the customer this transaction belongs to
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        platformCustomerId:
          type: string
          description: Platform-specific ID of the customer this transaction belongs to
          example: 18d3e5f7b4a9c2
        settledAt:
          type: string
          format: date-time
          description: When the payment was or will be settled
          example: '2025-08-15T14:30:00Z'
        createdAt:
          type: string
          format: date-time
          description: When the transaction was created
          example: '2025-08-15T14:25:18Z'
        updatedAt:
          type: string
          format: date-time
          description: When the transaction was last updated
          example: '2025-08-15T14:30:00Z'
        receiptDeliveryConfirmedAt:
          type: string
          format: date-time
          description: The time at which the platform confirmed delivery of the receipt to their customer.
          example: '2025-08-15T14:31:00Z'
        agentId:
          type: string
          description: If this transaction was initiated by an agent, the system-generated ID of that agent. Absent for platform-initiated transactions.
          example: Agent:019542f5-b3e7-1d02-0000-000000000042
        description:
          type: string
          description: Optional memo or description for the payment
          example: 'Payment for invoice #1234'
        sentAmount:
          $ref: '#/components/schemas/CurrencyAmount'
          description: Amount sent in the sender's currency
        exchangeRate:
          type: number
          description: 'Number of sending currency units per receiving currency unit. The rate is fee-exclusive: Grid deducts fees from the sending amount before converting at this rate.'
          exclusiveMinimum: 0
          example: 1.08
        quoteId:
          type: string
          description: The ID of the quote that was used to trigger this payment
          example: Quote:019542f5-b3e7-1d02-0000-000000000006
        refund:
          $ref: '#/components/schemas/Refund'
          description: The refund if transaction was refunded.
        counterpartyInformation:
          $ref: '#/components/schemas/CounterpartyInformation'
    TransactionSourceType:
      type: string
      enum:
        - ACCOUNT
        - UMA_ADDRESS
        - REALTIME_FUNDING
      description: Type of transaction source
      example: ACCOUNT
    BaseTransactionSource:
      type: object
      required:
        - sourceType
      properties:
        sourceType:
          $ref: '#/components/schemas/TransactionSourceType'
        currency:
          type: string
          description: Currency code for the source
          example: USD
    AccountTransactionSource:
      title: Account Source
      allOf:
        - $ref: '#/components/schemas/BaseTransactionSource'
        - type: object
          required:
            - accountId
            - sourceType
          properties:
            sourceType:
              type: string
              enum:
                - ACCOUNT
            accountId:
              type: string
              description: Source account identifier
              example: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
            railDetails:
              $ref: '#/components/schemas/RailDetailsOneOf'
            onChainTransaction:
              $ref: '#/components/schemas/OnChainTransaction'
              deprecated: true
              description: Deprecated. Use `railDetails`, which carries the same hash and network when `paymentRail` is `ONCHAIN`.
          description: Source account details
    UmaAddressTransactionSource:
      title: UMA Address Source
      allOf:
        - $ref: '#/components/schemas/BaseTransactionSource'
        - type: object
          required:
            - umaAddress
            - sourceType
          properties:
            sourceType:
              type: string
              enum:
                - UMA_ADDRESS
            umaAddress:
              type: string
              description: UMA address of the sender
              example: $sender@uma.domain.com
          description: UMA address source details
    RealtimeFundingTransactionSource:
      title: External Funding Source
      allOf:
        - $ref: '#/components/schemas/BaseTransactionSource'
        - type: object
          required:
            - currency
            - sourceType
          properties:
            sourceType:
              type: string
              enum:
                - REALTIME_FUNDING
            customerId:
              type: string
              description: The customer on whose behalf the transaction was initiated.
              example: Customer:019542f5-b3e7-1d02-0000-000000000009
            currency:
              type: string
              description: Currency code for the funding source
              example: USDC
            accountHolderName:
              type: string
              deprecated: true
              description: Deprecated. Use `railDetails.bankAccount.accountHolderName`. The name of the originator (sender) of the payment. Still populated on rails without a `railDetails` variant, such as SWIFT.
              example: John Sender
            accountIdentifier:
              type: string
              deprecated: true
              description: Deprecated. Use `railDetails.bankAccount.accountNumber`. The originator's account number or IBAN. May be masked or partial depending on the rail. Still populated on rails without a `railDetails` variant, such as SWIFT.
              example: '****6789'
            bankName:
              type: string
              deprecated: true
              description: Deprecated. Use `railDetails.bankAccount.bankName` on wires. The name of the originating bank. Still populated on rails without a `railDetails` variant, such as SWIFT.
              example: Chase Bank
            bankIdentifier:
              type: string
              deprecated: true
              description: Deprecated. Use `railDetails.bankAccount.routingNumber`. The identifier of the originating bank, such as a routing number, BIC, or SWIFT code. Still populated on rails without a `railDetails` variant, such as SWIFT.
              example: '021000021'
            paymentRail:
              deprecated: true
              description: Deprecated. Use `railDetails.paymentRail`. The payment rail the funds arrived on. Still populated on rails without a `railDetails` variant, such as SWIFT.
              allOf:
                - $ref: '#/components/schemas/PaymentRail'
            remittanceInformation:
              type: string
              deprecated: true
              description: Deprecated. Use `railDetails.addenda` for ACH, `railDetails.originatorToBeneficiaryInformation` for wires and `railDetails.remittanceInformation` for RTP and FedNow. Free-form information about the payment provided by the originator. Still populated on rails without a `railDetails` variant, such as SWIFT.
              example: '12345'
            endToEndId:
              type: string
              deprecated: true
              description: Deprecated. Use `railDetails.endToEndId`. The originator's own end-to-end reference for the payment. Still populated on rails without a `railDetails` variant, such as SWIFT.
              example: E2E-9f2c6b6f
            traceNumber:
              type: string
              deprecated: true
              description: Deprecated. Use `railDetails.traceNumber` for ACH and `railDetails.imad` for wires. Rail-level tracking identifier for the payment. Still populated on rails without a `railDetails` variant, such as SWIFT.
              example: '021000020123456'
            railDetails:
              $ref: '#/components/schemas/RailDetailsOneOf'
            onChainTransaction:
              $ref: '#/components/schemas/OnChainTransaction'
              deprecated: true
              description: Deprecated. Use `railDetails`, which carries the same hash and network when `paymentRail` is `ONCHAIN`.
          description: Transaction was funded using an external funding source. `railDetails` describes the transfer that delivered the funds; its fields are populated on a best-effort basis depending on what the funding source provides. Rails without a `railDetails` variant, such as SWIFT, carry the originator's details in the deprecated flat fields only.
    TransactionSourceOneOf:
      oneOf:
        - $ref: '#/components/schemas/AccountTransactionSource'
        - $ref: '#/components/schemas/UmaAddressTransactionSource'
        - $ref: '#/components/schemas/RealtimeFundingTransactionSource'
      discriminator:
        propertyName: sourceType
        mapping:
          ACCOUNT: '#/components/schemas/AccountTransactionSource'
          UMA_ADDRESS: '#/components/schemas/UmaAddressTransactionSource'
          REALTIME_FUNDING: '#/components/schemas/RealtimeFundingTransactionSource'
    ReconciliationInstructions:
      type: object
      minProperties: 1
      description: Instructions for reconciling a payment with this transaction. For the on-chain transaction to or from an external crypto wallet that is the transaction's own source or destination, use the `railDetails` on the relevant source or destination instead.
      properties:
        reference:
          type: string
          description: Unique reference code to include with the payment to match it with the correct incoming transaction, when available.
          example: UMA-Q12345-REF
        transactionHash:
          deprecated: true
          type: string
          description: |-
            Transaction hash of the settlement transfer, when available. This field reports two different transfers, and only one of them has a replacement today:

            For a crypto transfer to or from a customer's own external wallet, use the `railDetails` on the relevant source or destination instead — it names the network alongside the hash. That is the transfer this field is deprecated for.

            For the inter-VASP settlement leg of a UMA payment (e.g. USDC on Solana to the receiving partner), this field remains the only place the hash is reported: a UMA address is not a wallet you hold, so its source and destination carry no `railDetails`. The field will not be removed before that leg has a replacement.
          example: '0x9f2c6b6f4b6c8f2a8d9e0b1c2d3e4f5061728394a5b6c7d8e9f00112233445566'
    IncomingTransactionFailureReason:
      type: string
      enum:
        - LNURLP_FAILED
        - PAY_REQUEST_FAILED
        - PAYMENT_APPROVAL_WEBHOOK_ERROR
        - PAYMENT_APPROVAL_TIMED_OUT
        - OFFRAMP_FAILED
        - MISSING_MANDATORY_PAYEE_DATA
        - QUOTE_EXPIRED
        - QUOTE_EXECUTION_FAILED
        - COMPLIANCE_REJECTED
        - COLLECTION_FAILED
        - SWEEP_AMOUNT_OUT_OF_RANGE
        - SWEEP_QUOTE_FAILED
      description: |-
        Reason for failure of an incoming transaction. This is used to provide more context on why a transaction failed. If the transaction is not in a failed state, this field is omitted. COMPLIANCE_REJECTED means compliance blocked the incoming funds. COLLECTION_FAILED means an inbound bank-rail collection deposit (e.g. a SWIFT wire) failed after arrival and did not credit — most commonly a compliance-screening rejection or an unresolved request for information, on either the collection provider's side or Grid's. Failed collections are returned to the sender per the collection provider's process.
        A payment that settled into a rule-based account but could not be swept to the destination of the account's rule fails with one of the `SWEEP_*` reasons. A rule-based account never holds a balance, so the funds are moved to the account holder's own account in the same currency. SWEEP_AMOUNT_OUT_OF_RANGE means the amount was outside the band the corridor to the destination can carry — the rule's minimumAmount and maximumAmount give the current band — and SWEEP_QUOTE_FAILED that Grid could not price it. A sweep that was priced and then failed on the way to settlement reports QUOTE_EXECUTION_FAILED, the same as any other payment.
    IncomingTransaction:
      title: Incoming Transaction
      allOf:
        - $ref: '#/components/schemas/Transaction'
        - type: object
          required:
            - type
          properties:
            type:
              type: string
              enum:
                - INCOMING
            source:
              $ref: '#/components/schemas/TransactionSourceOneOf'
            receivedAmount:
              $ref: '#/components/schemas/CurrencyAmount'
              description: Amount received in the recipient's currency. This is only absent for rule-based account sweeps if the sweep couldn't be quoted. It's always present otherwise.
            fees:
              type: integer
              format: int64
              description: The total fees available from the receive quote in the smallest unit of the sending currency (eg. cents).
              minimum: 0
              example: 10
            reconciliationInstructions:
              $ref: '#/components/schemas/ReconciliationInstructions'
              description: Included for all transactions except those with "CREATED" status
            failureReason:
              $ref: '#/components/schemas/IncomingTransactionFailureReason'
              description: If the transaction failed, this field provides the reason for failure.
    OutgoingTransactionStatus:
      type: string
      enum:
        - PENDING
        - PENDING_AUTHORIZATION
        - EXPIRED
        - PROCESSING
        - COMPLETED
        - FAILED
      description: |
        Status of an outgoing payment transaction.

        | Status | Description |
        |--------|-------------|
        | `PENDING` | Quote is pending confirmation |
        | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU). The challenge is carried by the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` via `POST /quotes/{quoteId}/authorize`. |
        | `EXPIRED` | Quote wasn't executed before expiry window. Terminal |
        | `PROCESSING` | Executing the quote after receiving funds |
        | `COMPLETED` | The funds are at the destination. A bank return moves the payout to `FAILED` |
        | `FAILED` | The payout did not reach the destination, or was returned after it did — accompanied by a `failureReason`, and refunded to the source. A returned payout stays `FAILED` even if the return is later dishonored, since the refund has already been posted; the dishonor shows on `destination.railDetails` |
    OutgoingRateDetails:
      description: 'Details about the rate and fees for an outgoing transaction or quote. Note: `counterpartyFixedFee` is denominated in the receiving currency, so its equivalent value in the sending currency fluctuates with the FX rate. As a result, the total fee on a subsequent quote for the same transfer may differ even if the underlying fee structure is unchanged.'
      type: object
      required:
        - counterpartyMultiplier
        - counterpartyFixedFee
        - gridApiMultiplier
        - gridApiFixedFee
        - gridApiVariableFeeRate
        - gridApiVariableFeeAmount
      properties:
        counterpartyMultiplier:
          type: number
          format: double
          description: The underlying multiplier from mSATs to the receiving currency as returned by the counterparty institution.
          exclusiveMinimum: 0
          example: 1.08
        counterpartyFixedFee:
          type: integer
          format: int64
          description: The fixed fee charged by the counterparty institution to execute the quote in the smallest unit of the receiving currency (eg. cents).
          minimum: 0
          example: 10
        gridApiMultiplier:
          type: number
          format: double
          description: The underlying multiplier from the sending currency to mSATS, including variable fees.
          exclusiveMinimum: 0
          example: 0.925
        gridApiFixedFee:
          type: integer
          format: int64
          description: The fixed fee charged by the Grid product to execute the quote in the smallest unit of the sending currency (eg. cents).
          minimum: 0
          example: 10
        gridApiVariableFeeRate:
          type: number
          format: double
          description: The variable fee rate charged by the Grid product to execute the quote as a percentage of the sending currency amount.
          minimum: 0
          example: 0.003
        gridApiVariableFeeAmount:
          type: number
          format: int64
          description: The variable fee amount charged by the Grid product to execute the quote in the smallest unit of the sending currency (eg. cents). This is the sending amount times gridApiVariableFeeRate.
          minimum: 0
          example: 30
    OutgoingTransactionFailureReason:
      type: string
      enum:
        - QUOTE_EXPIRED
        - QUOTE_RATE_UNAVAILABLE
        - QUOTE_EXECUTION_FAILED
        - FUNDING_AMOUNT_MISMATCH
        - SCA_NOT_COMPLETED
        - PAYOUT_RETURNED
        - LIMIT_EXCEEDED
        - ACCOUNT_CANNOT_RECEIVE
        - ACCOUNT_INVALID
        - COMPLIANCE_REJECTED
        - LIGHTNING_PAYMENT_FAILED
        - COUNTERPARTY_POST_TX_FAILED
        - SWEEP_AMOUNT_OUT_OF_RANGE
        - SWEEP_QUOTE_FAILED
      description: |
        Reason for failure of an outgoing transaction. This is used to provide more
        context on why a transaction failed. If the transaction is not in a failed
        state, this field is omitted.

        When a payout partner rejects or fails a payment, one of the granular payout
        reasons (`PAYOUT_RETURNED`, `LIMIT_EXCEEDED`, `ACCOUNT_CANNOT_RECEIVE`,
        `ACCOUNT_INVALID`, `COMPLIANCE_REJECTED`) is returned when available.
        `COUNTERPARTY_POST_TX_FAILED` is a coarse fallback used only when no granular
        reason is available.

        `SCA_NOT_COMPLETED` means the customer did not satisfy the Strong Customer
        Authentication challenge before it expired, so the transaction was never
        authorized and no funds were moved. Only occurs for customers in a region
        where SCA is required (e.g. the EU). Create a new quote to try again, and have
        the customer authorize it while the challenge is live.

        | Reason | Description |
        |--------|-------------|
        | `QUOTE_EXPIRED` | The quote was not executed before its expiry window |
        | `QUOTE_RATE_UNAVAILABLE` | The quoted exchange rate was no longer available when the quote was executed, so nothing was exchanged. Create a new quote to get a current rate |
        | `QUOTE_EXECUTION_FAILED` | The quote could not be executed. Covers any internal failure on the way to settlement, whether or not funds were debited — a debited amount is refunded automatically |
        | `FUNDING_AMOUNT_MISMATCH` | The funds received did not match the expected amount |
        | `SCA_NOT_COMPLETED` | The customer did not complete the Strong Customer Authentication challenge before it expired |
        | `PAYOUT_RETURNED` | The receiving bank accepted the payout and then returned or reversed it |
        | `LIMIT_EXCEEDED` | The payout exceeds a recipient-, account-, or corridor-level limit at the partner |
        | `ACCOUNT_CANNOT_RECEIVE` | The account exists but can't accept this payment — dormant, frozen, restricted, or the currency or payment rail isn't supported for that account. A sweep whose rule names a rail the destination cannot accept reports this |
        | `ACCOUNT_INVALID` | The recipient account couldn't be found or the details are wrong — bad IBAN/account number, beneficiary not found, or account closed |
        | `COMPLIANCE_REJECTED` | The payout partner rejected the recipient on compliance grounds — sanctions, watchlist, or KYC/AML screening |
        | `LIGHTNING_PAYMENT_FAILED` | Deprecated — superseded by `QUOTE_EXECUTION_FAILED`. The Lightning settlement leg failed |
        | `COUNTERPARTY_POST_TX_FAILED` | Deprecated — coarse fallback retained for historical transactions when no granular payout reason is available |
        | `SWEEP_AMOUNT_OUT_OF_RANGE` | The amount was outside the range the corridor to the rule's destination can carry — below its minimum or above its maximum. The rule's `minimumAmount` and `maximumAmount` give the current band |
        | `SWEEP_QUOTE_FAILED` | Grid could not price the sweep to the rule's destination. A sweep that was priced and then failed on the way to settlement reports `QUOTE_EXECUTION_FAILED`, the same as any other payout |

        The `SWEEP_*` reasons apply to a payment that settled into a rule-based
        account and could not be swept onward. A rule-based account never holds a
        balance, so the funds are moved to the account holder's own account in the
        same currency.
    RailSelectionMode:
      type: string
      enum:
        - AUTO
        - MANUAL
      description: How the payment rail was chosen — MANUAL when the platform specified a paymentRail on the destination, AUTO when Lightspark selects it.
      example: AUTO
    OutgoingTransaction:
      title: Outgoing Transaction
      allOf:
        - $ref: '#/components/schemas/Transaction'
        - type: object
          required:
            - type
            - sentAmount
            - source
          properties:
            status:
              $ref: '#/components/schemas/OutgoingTransactionStatus'
            type:
              type: string
              enum:
                - OUTGOING
            source:
              $ref: '#/components/schemas/TransactionSourceOneOf'
            sentAmount:
              $ref: '#/components/schemas/CurrencyAmount'
              description: Amount sent in the sender's currency
            receivedAmount:
              $ref: '#/components/schemas/CurrencyAmount'
              description: Amount to be received by recipient in the recipient's currency
            fees:
              type: integer
              format: int64
              description: The fees associated with the quote in the smallest unit of the sending currency (eg. cents).
              minimum: 0
              example: 10
            platformFees:
              type: integer
              format: int64
              description: The portion of `fees` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `fees`.
              minimum: 0
              example: 5
            reconciliationInstructions:
              $ref: '#/components/schemas/ReconciliationInstructions'
              description: Reconciliation details for this transaction. For the on-chain hash of a crypto payout to an external wallet, see the destination's `railDetails` instead.
            paymentInstructions:
              type: array
              description: Payment instructions for executing the payment.
              items:
                $ref: '#/components/schemas/PaymentInstructions'
              example:
                - accountOrWalletInfo:
                    accountType: USD_ACCOUNT
                    paymentRails:
                      - ACH
                      - WIRE
                    accountNumber: '1234567890'
                    routingNumber: '021000021'
                    bankName: Chase Bank
                    reference: UMA-Q12345-REF
                  instructionsNotes: Include reference UMA-Q12345-REF in memo
                - accountOrWalletInfo:
                    accountType: SPARK_WALLET
                    assetType: BTC
                    address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu
                    invoice: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs
            rateDetails:
              $ref: '#/components/schemas/OutgoingRateDetails'
              description: Details about the rate and fees for the transaction.
            failureReason:
              $ref: '#/components/schemas/OutgoingTransactionFailureReason'
              description: If the transaction failed, this field provides the reason for failure.
            paymentRail:
              anyOf:
                - $ref: '#/components/schemas/PaymentRail'
                - type: 'null'
              description: The payment rail used to settle this transaction (e.g. ACH, WIRE, NEFT, FASTER_PAYMENTS). Uses the same values as the PaymentRail sent on quote requests. Null when no external rail is used (e.g. instant or intra-network transfers, or non-direct-destination transactions) or before a rail is resolved.
            railSelectionMode:
              $ref: '#/components/schemas/RailSelectionMode'
              description: How the rail was chosen — MANUAL when the platform specified a paymentRail on the destination, AUTO when Lightspark selects it.
            expectedSettlementAt:
              type:
                - string
                - 'null'
              format: date-time
              description: Expected settlement time at the beneficiary. Null for instant rails (settlement is immediate) and before a rail with deferred settlement is resolved.
            settlementTimelineSeconds:
              type:
                - integer
                - 'null'
              description: Expected number of seconds from quote creation to settlement. Null when not yet known.
    CardTransactionStatus:
      type: string
      enum:
        - AUTHORIZED
        - PARTIALLY_SETTLED
        - SETTLED
        - DECLINED
        - VOIDED
        - EXCEPTION
      description: |
        Lifecycle status of a card transaction. The status tracks the authorization
        outcome and settlement — a return against a purchase is its own dated
        `CREDIT` row linked to the purchase via `originalTransactionId`, not a
        status of its own.

        | Status | Description |
        |--------|-------------|
        | `AUTHORIZED` | The auth has been approved and a hold placed on the funding source; no clearing has arrived yet. |
        | `PARTIALLY_SETTLED` | At least one clearing has arrived and posted, but more clearings are still expected (split shipments, tips, multi-leg trips). |
        | `SETTLED` | All clearings for the auth have posted and the transaction is closed against the funding source. A `RETURN` received afterwards keeps the purchase `SETTLED`; the return appears as its own `CREDIT` transaction. |
        | `DECLINED` | The authorization was declined before any money moved. Declines carry no settlement and must be excluded from cardholder statements. |
        | `VOIDED` | The authorization was fully reversed or expired before any clearing posted, so the hold closed without moving money. `authorizedAmount` reports what is still held, normally 0, and `settledAmount` is absent. |
        | `EXCEPTION` | The transaction settled to the card network but the corresponding pull from the funding source failed (e.g. balance no longer covers the post-hoc clearing). Surfaces high-urgency alerts and is the dashboard query for stuck reconciliations. |
    CardDeclinedReason:
      type: string
      enum:
        - CARD_NOT_ACTIVE
        - SPEND_LIMIT_EXCEEDED
        - INSUFFICIENT_FUNDS
        - NO_ELIGIBLE_FUNDING_SOURCE
        - BLOCKED
        - UNSUPPORTED_NETWORK
        - OTHER
      description: |
        Why Grid declined a card authorization. Present only when a card
        transaction's `status` is `DECLINED`.

        | Reason | Returned when |
        |--------|---------------|
        | `CARD_NOT_ACTIVE` | The card is not `ACTIVE`. A `FROZEN`, `PROCESSING`, `PENDING_KYC`, or `CLOSED` card declines every authorization. |
        | `SPEND_LIMIT_EXCEEDED` | The authorization exceeds `maxSpendPerTransaction`, `maxSpendPerDay`, `maxTransactionsPerDay`, or the matching platform cap. |
        | `INSUFFICIENT_FUNDS` | Sandbox only. Production does not check the funding source balance when it decides an authorization, so an underfunded card approves and then resolves as `EXCEPTION` when the pull fails. |
        | `NO_ELIGIBLE_FUNDING_SOURCE` | The card has no usable funding source, for example an Embedded Wallet source with no active delegated key. |
        | `BLOCKED` | Program rules block the merchant category or the transaction. |
        | `UNSUPPORTED_NETWORK` | Grid does not support the network or the transaction type. |
        | `OTHER` | Any other reason, including a decline the card processor made on its own. |
    CardMerchant:
      type: object
      required:
        - descriptor
      properties:
        descriptor:
          type: string
          description: Merchant descriptor string captured from the card network at authorization time. On the sandbox simulate endpoints, where this is supplied rather than reported, it must be 1–25 characters — the width of the card network's acceptor-name field.
          example: BLUE BOTTLE COFFEE SF
        mcc:
          type: string
          description: Merchant Category Code (ISO 18245) — four-digit numeric string.
          example: '5814'
        city:
          type: string
          description: Merchant city as reported by the card network, when present.
          example: San Francisco
        state:
          type: string
          description: Merchant state or region as reported by the card network, when present.
          example: CA
        country:
          type: string
          description: Two-letter ISO 3166-1 alpha-2 country code of the merchant.
          example: US
    CardTransaction:
      title: Card Transaction
      type: object
      required:
        - type
        - id
        - status
        - direction
        - merchant
        - authorizedAmount
        - accountId
        - customerId
        - platformCustomerId
        - authorizedAt
        - createdAt
        - updatedAt
      description: One row per cardholder-visible card transaction. A purchase row rolls its clearings up into `settledAmount`; a merchant return is its own dated `CREDIT` row linked back to the purchase via `originalTransactionId` rather than a rollup on the parent, so statements can list purchases and refunds as separate dated lines. Delivered whole as the `data` payload of every `CARD_TRANSACTION.*` webhook, and returned as the `CARD` variant of `Transaction` from `GET /transactions`.
      properties:
        type:
          type: string
          enum:
            - CARD
          description: Discriminator identifying this transaction as a card transaction in the `Transaction` list.
        id:
          type: string
          description: System-generated unique card transaction identifier
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
        cardId:
          type: string
          description: The id of the `Card` this transaction was made on.
          example: Card:019542f5-b3e7-1d02-0000-000000000010
        customerId:
          type: string
          description: System ID of the customer (cardholder) this transaction belongs to.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        platformCustomerId:
          type: string
          description: Platform-specific ID of the customer (cardholder) this transaction belongs to.
          example: 18d3e5f7b4a9c2
        issuerTransactionToken:
          type: string
          description: Opaque identifier for the transaction on the underlying issuer. Used to cross-reference Grid records against issuer dashboards and webhooks.
          example: lithic_txn_b81c2a4f
        status:
          $ref: '#/components/schemas/CardTransactionStatus'
        cardDeclinedReason:
          $ref: '#/components/schemas/CardDeclinedReason'
          description: Present only when `status` is `DECLINED`.
          example: INSUFFICIENT_FUNDS
        direction:
          $ref: '#/components/schemas/TransactionDirection'
          description: A purchase is a `DEBIT`. A merchant refund is a `CREDIT` with the credited value in `settledAmount`; when the refund returns against a known purchase, `originalTransactionId` identifies it.
          example: DEBIT
        originalTransactionId:
          type: string
          description: 'On a refund row (`direction: CREDIT`), the id of the purchase this return credits back against. Absent on purchases and on standalone credits with no matching purchase.'
          example: Transaction:019542f5-b3e7-1d02-0000-000000000099
        description:
          type: string
          description: The merchant descriptor, repeated from `merchant.descriptor` so a transaction list reads without expanding each card row. Unlike `description` on other transaction types, this is set by Grid from the card network's descriptor, not supplied by the platform.
          example: BLUE BOTTLE COFFEE SF
        merchant:
          $ref: '#/components/schemas/CardMerchant'
        authorizedAmount:
          $ref: '#/components/schemas/CurrencyAmount'
        settledAmount:
          $ref: '#/components/schemas/CurrencyAmount'
        refundedAmount:
          $ref: '#/components/schemas/CurrencyAmount'
        accountId:
          type: string
          description: Internal account id that funded this transaction (the funding source selected by Authorization Decisioning at auth time).
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        authorizedAt:
          type: string
          format: date-time
          description: When the authorization was approved or declined.
          example: '2026-05-08T14:30:00Z'
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp (same as `authorizedAt` for card transactions).
          example: '2026-05-08T14:30:00Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp.
          example: '2026-05-08T15:42:11Z'
    TransactionOneOf:
      oneOf:
        - $ref: '#/components/schemas/IncomingTransaction'
        - $ref: '#/components/schemas/OutgoingTransaction'
        - $ref: '#/components/schemas/CardTransaction'
      discriminator:
        propertyName: type
        mapping:
          INCOMING: '#/components/schemas/IncomingTransaction'
          OUTGOING: '#/components/schemas/OutgoingTransaction'
          CARD: '#/components/schemas/CardTransaction'
    ExternalAccountDestinationReference:
      type: object
      required:
        - accountId
      properties:
        accountId:
          type: string
          description: Reference to an external account ID
          example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
        paymentRail:
          description: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail.
          allOf:
            - $ref: '#/components/schemas/PaymentRail'
    TransferOutRequest:
      type: object
      required:
        - source
        - destination
      properties:
        source:
          $ref: '#/components/schemas/InternalAccountReference'
          description: Source internal account details
        destination:
          $ref: '#/components/schemas/ExternalAccountDestinationReference'
          description: Destination external account details
        amount:
          type: integer
          format: int64
          description: Amount in the smallest unit of the currency (e.g., cents for USD/EUR, satoshis for BTC)
          example: 12550
        remittanceInformation:
          type: string
          maxLength: 80
          description: 'Free-form information about the payment that travels with it to the recipient. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.'
          example: '12345'
        purposeOfPayment:
          $ref: '#/components/schemas/PurposeOfPayment'
    CurrencyPreference:
      type: object
      required:
        - currency
        - estimatedExchangeRate
        - min
        - max
      properties:
        currency:
          $ref: '#/components/schemas/Currency'
        estimatedExchangeRate:
          type: number
          description: An estimated exchange rate from the sender's currency to this currency. This is not a locked rate and is subject to change when calling the quotes endpoint.
          exclusiveMinimum: 0
          example: 1.08
        exchangeRate:
          type: number
          description: An estimated number of sending currency units per unit of this currency, in the same direction and units as `exchangeRate` on a quote. This is not a locked rate and is subject to change when calling the quotes endpoint.
          exclusiveMinimum: 0
          example: 0.925
        min:
          type: integer
          format: int64
          description: The minimum amount that can be received in this currency.
          exclusiveMinimum: 0
          example: 1
        max:
          type: integer
          format: int64
          description: The maximum amount that can be received in this currency.
          exclusiveMinimum: 0
          example: 1000000
        minSendingAmount:
          type: integer
          format: int64
          description: The minimum amount that can be sent for this currency, in the smallest unit of the sender's currency (e.g. cents for USD). Same semantics as `minSendingAmount` on the exchange rates endpoint. This is an estimate based on the current exchange rate and is subject to change when calling the quotes endpoint. Omitted when the sending-side bound cannot be resolved.
          minimum: 0
          example: 100
        maxSendingAmount:
          type: integer
          format: int64
          description: The maximum amount that can be sent for this currency, in the smallest unit of the sender's currency (e.g. cents for USD). Same semantics as `maxSendingAmount` on the exchange rates endpoint. This is an estimate based on the current exchange rate and is subject to change when calling the quotes endpoint. Omitted when the sending-side bound cannot be resolved.
          minimum: 0
          example: 500000
    ReceiverLookupResponse:
      type: object
      required:
        - sendingCurrency
        - supportedCurrencies
        - lookupId
      properties:
        sendingCurrency:
          allOf:
            - $ref: '#/components/schemas/Currency'
            - description: The currency the payment is sent from — the sender's default, or the one named by the `sendingCurrency` query parameter. Every `estimatedExchangeRate` in `supportedCurrencies` converts from this currency, and any `minSendingAmount`/`maxSendingAmount` is denominated in its smallest unit.
        supportedCurrencies:
          type: array
          description: List of currencies supported by the receiving account
          items:
            $ref: '#/components/schemas/CurrencyPreference'
        requiredPayerDataFields:
          type: array
          description: Fields required by the receiving institution about the payer before payment can be completed
          items:
            $ref: '#/components/schemas/CounterpartyFieldDefinition'
        lookupId:
          type: string
          description: Unique identifier for the lookup. Needed in the subsequent create quote request.
          example: Lookup:019542f5-b3e7-1d02-0000-000000000009
    ReceiverUmaLookupResponse:
      allOf:
        - $ref: '#/components/schemas/ReceiverLookupResponse'
        - type: object
          required:
            - receiverUmaAddress
          properties:
            receiverUmaAddress:
              type: string
              description: The UMA address that was looked up
              example: $receiver@uma.domain
    Error412:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | UNSUPPORTED_UMA_VERSION | Counterparty doesn't support the Grid UMA version |
          enum:
            - UNSUPPORTED_UMA_VERSION
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error502:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | INVALID_PAYREQ_RESPONSE | Counterparty payment request response was invalid. Retryable |
            | COUNTERPARTY_SERVER_ERROR | Counterparty returned a server error. Retryable |
          enum:
            - INVALID_PAYREQ_RESPONSE
            - COUNTERPARTY_SERVER_ERROR
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error504:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | LOOKUP_REQUEST_FAILED | Network error or timeout reaching the counterparty during lookup. Retryable |
            | QUOTE_REQUEST_FAILED | Network error or timeout sending the quote request to the counterparty. Retryable |
          enum:
            - LOOKUP_REQUEST_FAILED
            - QUOTE_REQUEST_FAILED
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    ReceiverExternalAccountLookupResponse:
      allOf:
        - $ref: '#/components/schemas/ReceiverLookupResponse'
        - type: object
          required:
            - accountId
          properties:
            accountId:
              type: string
              description: The external account ID that was looked up
              example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
    PaymentDocumentType:
      type: string
      description: |-
        What a supporting document is. These are kinds of evidence, not file formats: `INVOICE` means the file is an invoice.

        [Supporting Documents](https://docs.lightspark.com/payouts-and-b2b/payment-flow/send-payment#supporting-documents) lists the types each requirement accepts. When you upload a file with `POST /payment-documents`, `documentType` declares which type the file is.
      enum:
        - PURCHASE_ORDER
        - LOGISTICS_BILL
        - CUSTOMS_DECLARATION
        - INVOICE
        - CONTRACT
        - DELIVERY_SLIP
        - BILL_OF_LADING
        - FLIGHT_TICKET
        - TRAVEL_DOCUMENT
        - HOTEL_BOOKING_CONFIRMATION
      example: INVOICE
    PaymentDocumentUploadRequest:
      title: Payment Document Upload Request
      type: object
      required:
        - file
        - documentType
      properties:
        file:
          type: string
          format: binary
          description: |
            The document file, from 1 to 8,000,000 bytes. Grid accepts PDF, JPEG,
            and PNG files and detects the format from the file contents, not the
            file name. An empty file, a larger file, and any other format return
            `400 INVALID_INPUT`.
        documentType:
          $ref: '#/components/schemas/PaymentDocumentType'
          description: What the file is. To fill a requirement, use one of the types it accepts, as listed in [Supporting Documents](https://docs.lightspark.com/payouts-and-b2b/payment-flow/send-payment#supporting-documents).
        customerId:
          type: string
          description: ID of the sending customer whose payment this document supports. The document can only be used on this customer's quotes. Omit it when the platform itself is the sender, as on a quote with no `customerId`. The document can then only be used on the platform's own quotes.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
    PaymentDocumentStatus:
      type: string
      description: |
        Where the payment document is in its lifecycle.

        | Status | Terminal | Meaning |
        |--------|----------|---------|
        | `UPLOADED` | No | Ready to use. Pass the `id` in `documentIds` on `POST /quotes` before `expiresAt`. |
        | `ATTACHED` | Yes | Attached to the payment behind the quote in `quoteId`. Grid has deleted the file. |
        | `EXPIRED` | Yes | Not attached to a quote before `expiresAt`. The document can no longer be used. |
      enum:
        - UPLOADED
        - ATTACHED
        - EXPIRED
      example: UPLOADED
    PaymentDocument:
      type: object
      description: A supporting document uploaded for a payout that requires one. Pass its `id` in `documentIds` on `POST /quotes`.
      required:
        - id
        - documentType
        - fileName
        - sizeBytes
        - contentType
        - status
        - expiresAt
        - createdAt
      properties:
        id:
          type: string
          description: Unique identifier for this payment document
          example: PaymentDocument:019542f5-b3e7-1d02-0000-000000000001
        customerId:
          type: string
          description: ID of the sending customer whose payment this document supports. The document can only be used on this customer's quotes. Absent when the platform itself is the sender.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        documentType:
          $ref: '#/components/schemas/PaymentDocumentType'
        fileName:
          type: string
          description: File name of the uploaded file
          example: invoice-2025-0142.pdf
        sizeBytes:
          type: integer
          description: Size of the uploaded file in bytes. It never exceeds the largest file `POST /payment-documents` accepts, which is 8,000,000 bytes today.
          minimum: 1
          example: 482133
        contentType:
          type: string
          description: File format as a MIME type, as Grid detected it from the file contents. Today one of `application/pdf`, `image/jpeg`, or `image/png`. Treat it as an open value, because Grid may accept more formats later.
          example: application/pdf
        status:
          $ref: '#/components/schemas/PaymentDocumentStatus'
        expiresAt:
          type: string
          format: date-time
          description: When the document stops being usable if it has not been attached to a quote. This is 24 hours after `createdAt`.
          example: '2025-10-04T12:00:00Z'
        quoteId:
          type: string
          description: ID of the quote whose payment this document is attached to. Present only when `status` is `ATTACHED`.
          example: Quote:019542f5-b3e7-1d02-0000-000000000006
        createdAt:
          type: string
          format: date-time
          description: When this document was uploaded
          example: '2025-10-03T12:00:00Z'
    QuoteSourceType:
      type: string
      enum:
        - ACCOUNT
        - REALTIME_FUNDING
      description: Type of quote funding source
      example: ACCOUNT
    BaseQuoteSource:
      type: object
      required:
        - sourceType
      properties:
        sourceType:
          $ref: '#/components/schemas/QuoteSourceType'
    AccountQuoteSource:
      title: Account
      allOf:
        - $ref: '#/components/schemas/BaseQuoteSource'
        - type: object
          required:
            - accountId
            - sourceType
          properties:
            sourceType:
              type: string
              enum:
                - ACCOUNT
            accountId:
              type: string
              description: Source account identifier
              example: InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
            customerId:
              type: string
              description: The customer on whose behalf the transaction is being initiated. Required when funding from a `BULK_SETTLEMENT` account. The customer is never the account owner, because the platform owns the account. When funding from an FBO account, this field is optional. If you omit it, the transaction is initiated on behalf of the platform. For any other account, it defaults to the account owner's customerId.
              example: Customer:019542f5-b3e7-1d02-0000-000000000001
          description: Source account details
    RealtimeFundingQuoteSource:
      title: Real-time Funding
      allOf:
        - $ref: '#/components/schemas/BaseQuoteSource'
        - type: object
          required:
            - currency
            - sourceType
          properties:
            sourceType:
              type: string
              enum:
                - REALTIME_FUNDING
            customerId:
              type: string
              description: Source customer ID. If this transaction is being initiated on behalf of a customer, this is required. If customerId is not provided, the quote will be created on behalf of the platform itself.
              example: Customer:019542f5-b3e7-1d02-0000-000000000009
            currency:
              type: string
              description: Currency code for the funding source. See [Supported Currencies](https://docs.lightspark.com/platform-overview/core-concepts/currencies-and-rails) for the full list of supported fiat and crypto currencies.
              example: USD
            cryptoNetwork:
              type: string
              description: 'The crypto network to use for the funding source. Required when `currency` is a stablecoin (e.g. USDC, USDT). Specifies which network the customer will deposit on, so the correct deposit address can be generated. Example values: `SOLANA`, `ETHEREUM`, `BASE`, `POLYGON`, `TRON`, `PLASMA`, `ARBITRUM`, `SPARK`, `LIGHTNING`, `BITCOIN`.'
              example: SOLANA
          description: Fund the quote using a real-time funding source (RTP, SEPA Instant, Spark, Stables, etc.). This will require manual just-in-time funding using `paymentInstructions` in the response. Because quotes expire quickly, this option is only valid for instant payment methods. Do not try to fund a quote with a non-instant payment method (ACH, etc.).
    QuoteSourceOneOf:
      oneOf:
        - $ref: '#/components/schemas/AccountQuoteSource'
        - $ref: '#/components/schemas/RealtimeFundingQuoteSource'
      discriminator:
        propertyName: sourceType
        mapping:
          ACCOUNT: '#/components/schemas/AccountQuoteSource'
          REALTIME_FUNDING: '#/components/schemas/RealtimeFundingQuoteSource'
    DestinationType:
      type: string
      enum:
        - ACCOUNT
        - UMA_ADDRESS
      description: Type of payment destination
      example: ACCOUNT
    BaseDestination:
      type: object
      required:
        - destinationType
      properties:
        destinationType:
          $ref: '#/components/schemas/DestinationType'
    AccountDestination:
      title: Account
      allOf:
        - $ref: '#/components/schemas/BaseDestination'
        - type: object
          required:
            - accountId
            - destinationType
          properties:
            destinationType:
              type: string
              enum:
                - ACCOUNT
            accountId:
              type: string
              description: Destination account identifier
              example: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
            paymentRail:
              allOf:
                - $ref: '#/components/schemas/PaymentRail'
                - description: The payment rail to use for the transfer. Must be one of the rails supported by the destination account. If not specified, the system will select a default rail.
          description: Destination account details
    UmaAddressDestination:
      title: UMA Address
      allOf:
        - $ref: '#/components/schemas/BaseDestination'
        - type: object
          required:
            - umaAddress
            - destinationType
            - currency
          properties:
            destinationType:
              type: string
              enum:
                - UMA_ADDRESS
            umaAddress:
              type: string
              maxLength: 200
              description: UMA address of the recipient
              example: $receiver@uma.domain.com
            currency:
              type: string
              description: Currency code for the destination. See [Supported Currencies](https://docs.lightspark.com/platform-overview/core-concepts/currencies-and-rails) for the full list of supported fiat and crypto currencies.
              example: EUR
          description: UMA address destination details
    QuoteDestinationOneOf:
      oneOf:
        - $ref: '#/components/schemas/AccountDestination'
        - $ref: '#/components/schemas/UmaAddressDestination'
      discriminator:
        propertyName: destinationType
        mapping:
          ACCOUNT: '#/components/schemas/AccountDestination'
          UMA_ADDRESS: '#/components/schemas/UmaAddressDestination'
    Quote:
      type: object
      required:
        - id
        - status
        - expiresAt
        - createdAt
        - source
        - destination
        - sendingCurrency
        - receivingCurrency
        - totalSendingAmount
        - totalReceivingAmount
        - exchangeRate
        - feesIncluded
        - transactionId
      properties:
        id:
          type: string
          description: Unique identifier for this quote
          example: Quote:019542f5-b3e7-1d02-0000-000000000006
        status:
          type: string
          enum:
            - PENDING
            - PENDING_AUTHORIZATION
            - PROCESSING
            - COMPLETED
            - FAILED
            - EXPIRED
          description: 'Current status of the quote. `PENDING_AUTHORIZATION` occurs only for customers in a region where Strong Customer Authentication is required (e.g. EU): the quote carries an `scaChallenge` that must be authorized before execution, and for realtime-funding sources `paymentInstructions` are withheld until it is satisfied.'
          example: PENDING
        createdAt:
          type: string
          format: date-time
          description: When this quote was created
          example: '2025-10-03T12:00:00Z'
        expiresAt:
          type: string
          format: date-time
          description: 'Absolute UTC timestamp when the rate locked in this quote becomes invalid and the quote can no longer be executed. The window depends on the rail and corridor: instant rails (Lightning, Spark, USDC on Solana/Base/Polygon, RTP, SEPA Instant) typically expire in 1–5 minutes; corridors with longer settlement guarantees may have longer windows. Always rely on this timestamp rather than assuming a fixed window.'
          example: '2025-10-03T12:05:00Z'
        source:
          $ref: '#/components/schemas/QuoteSourceOneOf'
        destination:
          $ref: '#/components/schemas/QuoteDestinationOneOf'
        sendingCurrency:
          $ref: '#/components/schemas/Currency'
          description: Currency for the sending amount
        receivingCurrency:
          $ref: '#/components/schemas/Currency'
          description: Currency for the receiving amount
        totalSendingAmount:
          type: integer
          format: int64
          description: The total amount that will be sent in the smallest unit of the sending currency (eg. cents).
          exclusiveMinimum: 0
          example: 123010
        totalReceivingAmount:
          type: integer
          format: int64
          description: The total amount that will be received in the smallest unit of the receiving currency (eg. cents).
          exclusiveMinimum: 0
          example: 1000
        exchangeRate:
          type: number
          description: 'Number of sending currency units per receiving currency unit. The rate is fee-exclusive: Grid deducts `feesIncluded` from `totalSendingAmount` first, then converts the remainder at this rate to reach `totalReceivingAmount`.'
          exclusiveMinimum: 0
        feesIncluded:
          type: integer
          format: int64
          description: 'The fees associated with the quote in the smallest unit of the sending currency (eg. cents). Note: this value may fluctuate between quotes — some underlying fee components are defined in the receiving currency, so their equivalent in the sending currency moves with the FX rate. The fees shown here are locked only for the lifetime of this quote.'
          minimum: 0
          example: 10
        platformFeesIncluded:
          type: integer
          format: int64
          description: The portion of `feesIncluded` collected by the platform (platform-configured transaction fees), in the smallest unit of the sending currency. 0 when the platform has no applicable fee configured. Already included in `feesIncluded`. May be omitted from payloads produced before platform fees existed.
          minimum: 0
          example: 5
        paymentInstructions:
          type: array
          description: Payment instructions for executing the payment. This is not required when using an internal account source.
          items:
            $ref: '#/components/schemas/PaymentInstructions'
          example:
            - accountOrWalletInfo:
                accountType: USD_ACCOUNT
                paymentRails:
                  - ACH
                  - WIRE
                accountNumber: '1234567890'
                routingNumber: '021000021'
                bankName: Chase Bank
                reference: UMA-Q12345-REF
              instructionsNotes: Include reference UMA-Q12345-REF in memo
            - accountOrWalletInfo:
                accountType: SPARK_WALLET
                assetType: BTC
                address: spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu
                invoice: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs
        transactionId:
          type: string
          description: The ID of the transaction created from this quote.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000005
        counterpartyInformation:
          $ref: '#/components/schemas/CounterpartyInformation'
          description: Additional information about the counterparty, if available and required by the platform in their configuration.
        rateDetails:
          $ref: '#/components/schemas/OutgoingRateDetails'
          description: Details about the rate and fees for the transaction.
        purposeOfPayment:
          $ref: '#/components/schemas/PurposeOfPayment'
          description: The purpose of payment the quote was created with. Present only when the quote request set `purposeOfPayment`.
        documentIds:
          type: array
          description: IDs of the supporting documents attached to the payment behind this quote, as passed in `documentIds` on `POST /quotes`. Present only when the quote was created with `documentIds`. Grid returns such a quote only after it has attached every document. Call `GET /payment-documents/{paymentDocumentId}` for each document's details, such as its `documentType` and its `ATTACHED` status.
          items:
            type: string
          example:
            - PaymentDocument:019542f5-b3e7-1d02-0000-000000000001
            - PaymentDocument:019542f5-b3e7-1d02-0000-000000000002
            - PaymentDocument:019542f5-b3e7-1d02-0000-000000000003
        scaChallenge:
          $ref: '#/components/schemas/ScaChallenge'
          description: 'Present only while `status` is `PENDING_AUTHORIZATION`: the Strong Customer Authentication challenge to satisfy before this quote can be executed (or, for realtime-funding sources, before `paymentInstructions` are issued). Omitted for customers outside SCA-regulated regions (non-EU).'
    QuoteLockSide:
      type: string
      enum:
        - SENDING
        - RECEIVING
      description: The side of the quote which should be locked and specified in the `lockedCurrencyAmount`. For example, if I want to send exactly $5 MXN from my wallet, I would set this to "sending", and the `lockedCurrencyAmount` to 500 (in cents). If I want the receiver to receive exactly $10 USD, I would set this to "receiving" and the `lockedCurrencyAmount` to 10000 (in cents).
    QuoteRequest:
      type: object
      required:
        - source
        - destination
        - lockedCurrencySide
        - lockedCurrencyAmount
      properties:
        lookupId:
          type: string
          description: |-
            Lookup ID from a previous receiver lookup request. If provided, this can make the quote creation more efficient by reusing cached lookup data.
            NOTE: This is required for UMA destinations due to counterparty institution requirements. See `senderCustomerInfo` for more information.
          example: Lookup:019542f5-b3e7-1d02-0000-000000000009
        source:
          $ref: '#/components/schemas/QuoteSourceOneOf'
        destination:
          $ref: '#/components/schemas/QuoteDestinationOneOf'
        lockedCurrencySide:
          $ref: '#/components/schemas/QuoteLockSide'
        lockedCurrencyAmount:
          type: integer
          format: int64
          description: The amount to send/receive in the smallest unit of the locked currency (eg. cents). See `lockedCurrencySide` for more information.
          exclusiveMinimum: 0
          maximum: 9000000000000000
          example: 1000
        immediatelyExecute:
          type: boolean
          description: |-
            Whether to immediately execute the quote after creation. If true, the quote will be executed and the transaction will be created at the current exchange rate. It should only be used if you don't want to lock and view rate details before executing the quote. If you are executing a pre-existing quote, use the `/quotes/{quoteId}/execute` endpoint instead. This is false by default.
            This can only be used for quotes with a `source` which is either an internal account, or has direct pull functionality (e.g. ACH pull with an external account).
            Not supported when the `source` is an internal account of type `EMBEDDED_WALLET`: those transfers require a `Grid-Wallet-Signature` over the `payloadToSign` returned in the quote response, which is not available in a combined create-and-execute call. Create the quote first with `immediatelyExecute: false` and then call `POST /quotes/{quoteId}/execute` with the `Grid-Wallet-Signature` stamp header.
          example: false
        description:
          type: string
          maxLength: 1024
          description: Optional description/memo for the transfer
          example: 'Invoice #1234 payment'
        remittanceInformation:
          type: string
          maxLength: 80
          description: 'Free-form information about the payment that travels with it to the recipient. The field this populates depends on the payment rail: for ACH it populates the Addenda record, for FedNow and RTP it populates the remittanceInformation field, and for wires it populates the OBI (Originator to Beneficiary Information) / beneficiary information.'
          example: '12345'
        purposeOfPayment:
          $ref: '#/components/schemas/PurposeOfPayment'
        documentIds:
          type: array
          minItems: 1
          maxItems: 3
          uniqueItems: true
          description: |-
            IDs of payment documents from `POST /payment-documents` that support this payment. Required when the destination requires supporting documents. Supply one document for each requirement of your `purposeOfPayment`. [Supporting Documents](https://docs.lightspark.com/payouts-and-b2b/payment-flow/send-payment#supporting-documents) lists which payouts need documents, the requirements for each purpose, and the document types each requirement accepts. Grid matches each document to a requirement by its `documentType`, which must be one of the types that requirement accepts. A request that leaves a requirement unfilled returns `400 DOCUMENTS_REQUIRED`. Each document must belong to the customer sending the payment, or to the platform when the platform itself is the sender. Any other ID returns `404 PAYMENT_DOCUMENT_NOT_FOUND`.

            Grid attaches every document to the payment before it returns the quote. With `immediatelyExecute: true`, Grid attaches the documents before it executes the quote. If an attachment fails, Grid creates no quote and returns `424 PAYMENT_DOCUMENT_ATTACHMENT_FAILED`. Retry the same request with the same `Idempotency-Key`.

            Requires the `Idempotency-Key` header. A destination that does not require supporting documents rejects `documentIds` with `400 INVALID_INPUT`.
          items:
            type: string
          example:
            - PaymentDocument:019542f5-b3e7-1d02-0000-000000000001
            - PaymentDocument:019542f5-b3e7-1d02-0000-000000000002
            - PaymentDocument:019542f5-b3e7-1d02-0000-000000000003
        platformFeeOverride:
          $ref: '#/components/schemas/PlatformFeeOverride'
        scaFactor:
          $ref: '#/components/schemas/ScaFactor'
          description: Optional preferred factor for a Strong Customer Authentication challenge issued at quote creation. Only relevant for a realtime-funding source in a region where SCA is required (e.g. EU); ignored otherwise. Valid values are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected. When the quote is returned in `PENDING_AUTHORIZATION`, authorize it via `POST /quotes/{quoteId}/authorize`.
        senderCustomerInfo:
          type: object
          additionalProperties: true
          description: |
            Key-value pairs of additional information about the sender which was requested by the destination. This is relevant when the destination requires more sender info than was provided during customer creation.
            Any fields specified in `requiredPayerDataFields` from the response of the `/receiver/uma/{receiverUmaAddress}` (lookupUma) or `/receiver/external-account/{accountId}` (lookupExternalAccount) endpoints MUST be provided here if they were requested. If the destination did not request any additional information, this field can be omitted.
          example:
            FULL_NAME: Jane Receiver
            NATIONALITY: FR
    Error408:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | REQUEST_TIMEOUT | Timed out reading the request; retry |
          enum:
            - REQUEST_TIMEOUT
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
    ExecuteQuoteRequest:
      type: object
      description: Optional body for executing a quote. Only needed to request a specific Strong Customer Authentication factor (`scaFactor`) for the challenge this call issues; omit the body entirely otherwise.
      properties:
        scaFactor:
          $ref: '#/components/schemas/ScaFactor'
          description: Optional preferred factor for the Strong Customer Authentication challenge this call issues. Only relevant for customers in a region where SCA is required (e.g. EU); ignored otherwise. Valid values for a per-transaction challenge are `SMS_OTP` (default) and `PASSKEY` — `TOTP` cannot carry the required dynamic linking and is rejected here. Omit to default to `SMS_OTP`.
    ScaAuthorization:
      type: object
      description: Proof that satisfies an `ScaChallenge`. Provide exactly one of `code` (for `SMS_OTP` / `TOTP`) or `passkeyAssertion` (for `PASSKEY`). When supplying a `passkeyAssertion`, `origin` is **required** — the WebAuthn assertion is bound to the origin it was produced against, and a passkey confirmation is rejected without it.
      properties:
        code:
          type:
            - string
            - 'null'
          description: The one-time code the customer received by SMS, or read from their authenticator app. In sandbox, the code is always `123456`.
          example: '123456'
        passkeyAssertion:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: Opaque WebAuthn assertion produced by the device from the challenge's `passkeyAssertionOptions`. Required when satisfying a `PASSKEY` challenge.
        origin:
          type:
            - string
            - 'null'
          description: The WebAuthn origin the `passkeyAssertion` was produced against. **Required** alongside `passkeyAssertion`; omit it for the `code` path. When the challenge lists `passkeyAllowedOrigins` (enrollment / login challenges), it must be one of those. A per-transaction passkey challenge carries `passkeyAssertionOptions` but may omit `passkeyAllowedOrigins`; in that case supply the origin your app invoked the WebAuthn API from, which must match the relying party in `passkeyAssertionOptions`.
          example: https://app.example.com
        endUserIpAddress:
          type:
            - string
            - 'null'
          description: 'The IP address of the end user''s device authorizing this operation, forwarded to the SCA provider where it feeds risk assessment and any transaction-risk exemption. Supply the customer''s address, not your server''s. Optional: the provider records it for money-movement authorizations, and ignores it for beneficiary trust changes.'
          example: 203.0.113.42
    TransactionListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of transactions matching the criteria
          items:
            $ref: '#/components/schemas/TransactionOneOf'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of transactions matching the criteria (excluding pagination)
    ConfirmReceiptDeliveryRequest:
      type: object
      properties:
        receiptDeliveryConfirmedAt:
          type: string
          format: date-time
          description: The time at which the platform delivered the transaction receipt to their customer. If omitted, the current server time is used.
          example: '2025-08-15T14:31:00Z'
    ApprovePaymentRequest:
      type: object
      properties:
        receiverCustomerInfo:
          type: object
          additionalProperties: true
          description: Information about the recipient, provided by the platform if requested in the original webhook via `requestedReceiverCustomerInfoFields`.
    RejectPaymentRequest:
      type: object
      properties:
        reason:
          type: string
          description: Optional reason for rejecting the payment. This is just for debugging purposes or can be used for a platform's own purposes.
          example: RESTRICTED_JURISDICTION
    CancelTransactionRequest:
      type: object
      properties:
        reason:
          type: string
          description: Optional reason for cancelling the transaction. This is just for debugging purposes or can be used for a platform's own purposes.
          example: REQUESTED_AFTER_HOURS
    EstimateCryptoWithdrawalFeeRequest:
      type: object
      required:
        - internalAccountId
        - currency
        - cryptoNetwork
        - amount
        - destinationAddress
      properties:
        internalAccountId:
          type: string
          description: The ID of the crypto internal account to withdraw from.
          example: InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
        currency:
          type: string
          description: The currency code of the asset to withdraw (e.g. USDC).
          example: USDC
        cryptoNetwork:
          type: string
          description: 'The blockchain network for the withdrawal. Example values: SOLANA, ETHEREUM, BASE, POLYGON, ARBITRUM, SPARK, LIGHTNING, BITCOIN.'
          example: SOLANA
        amount:
          type: integer
          description: The amount to withdraw in the smallest unit of the currency.
          example: 1000000
        destinationAddress:
          type: string
          description: The blockchain address to withdraw funds to.
          example: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
    EstimateCryptoWithdrawalFeeResponse:
      type: object
      required:
        - networkFee
        - networkFeeAsset
        - applicationFee
        - totalFee
        - netAmount
      properties:
        networkFee:
          type: integer
          description: The estimated network (gas) fee in the smallest unit of the network fee asset (e.g. lamports for SOL). This is provided for informational purposes to show the raw on-chain cost. Note that this value is denominated in networkFeeAsset, not in the withdrawal currency — it cannot be directly added to applicationFee or compared to totalFee.
          example: 5000
        networkFeeAsset:
          type: string
          description: The asset used to pay the network fee (e.g. SOL for Solana transactions).
          example: SOL
        applicationFee:
          type: integer
          description: The application fee charged by the platform in the smallest unit of the withdrawal currency. Zero if no application fee applies.
          example: 0
        totalFee:
          type: integer
          description: The total cost of the withdrawal in the smallest unit of the withdrawal currency. This equals the network fee converted to the withdrawal currency at current rates plus the application fee. This is the amount deducted from the withdrawal in addition to netAmount.
          example: 5000
        netAmount:
          type: integer
          description: The net amount the recipient will receive after fees, in the smallest unit of the withdrawal currency.
          example: 995000
    TestWebhookResponse:
      type: object
      required:
        - response_status
      properties:
        url:
          type: string
          format: uri
          description: URL where the webhook was sent
          example: https://api.mycompany.com/webhooks/uma
        response_status:
          type: integer
          description: The HTTP status code returned by the webhook endpoint
          example: 200
        response_body:
          type: string
          description: The raw body content returned by the webhook endpoint in response to the request
    BulkCustomerImportJobAccepted:
      type: object
      required:
        - jobId
        - status
      properties:
        jobId:
          type: string
          description: Unique identifier for the bulk import job
          example: Job:019542f5-b3e7-1d02-0000-000000000006
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
    GridError:
      type: object
      title: GridError
      properties:
        code:
          type: string
          description: Error code
        message:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    BulkCustomerImportErrorEntry:
      allOf:
        - $ref: '#/components/schemas/GridError'
        - type: object
          description: Error information for a failed bulk import entry
          required:
            - correlationId
          properties:
            correlationId:
              type: string
              description: Platform customer ID or row number for the failed entry
              example: biz456
    BulkCustomerImportJob:
      type: object
      required:
        - id
        - status
        - progress
      properties:
        id:
          type: string
          description: Unique identifier for the bulk import job
          example: Job:019542f5-b3e7-1d02-0000-000000000006
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
            - COMPLETED
            - FAILED
          description: Current status of the job
          example: PROCESSING
        progress:
          type: object
          required:
            - total
            - processed
            - successful
            - failed
          properties:
            total:
              type: integer
              description: Total number of customers to process
              example: 5000
            processed:
              type: integer
              description: Number of customers processed so far
              example: 2500
            successful:
              type: integer
              description: Number of customers successfully created
              example: 2450
            failed:
              type: integer
              description: Number of customers that failed to create
              example: 50
        errors:
          type: array
          description: Detailed error information for failed entries
          items:
            $ref: '#/components/schemas/BulkCustomerImportErrorEntry'
        completedAt:
          type: string
          format: date-time
          description: Timestamp when the job completed (only present for COMPLETED or FAILED status)
          example: '2025-08-15T14:32:00Z'
    UmaInvitationCreateRequest:
      type: object
      required:
        - inviterUma
      properties:
        inviterUma:
          type: string
          description: The UMA address of the customer creating the invitation
          example: $inviter@uma.domain
        firstName:
          type: string
          description: First name of the invitee to show as part of the invite
          example: Alice
        amountToSend:
          description: |
            An amount to send (in the smallest unit of the customer's currency) to the invitee when the invitation is claimed.
            This is optional and if not provided, the invitee will not receive any amount. Note that the actual sending of
            the amount must be done by the inviter platform once the INVITATION_CLAIMED webhook is received. If the inviter
            platform either does not send the payment or the payment fails, the invitee will not receive this amount. This
            field is primarily used for display purposes on the claiming side of the invitation.
          type: integer
          format: int64
          example: 12550
        expiresAt:
          type: string
          format: date-time
          description: When the invitation expires (if at all)
          example: '2025-09-01T14:30:00Z'
    UmaInvitation:
      type: object
      required:
        - code
        - createdAt
        - inviterUma
        - status
        - url
      properties:
        code:
          type: string
          description: The unique code of the invitation
          example: 019542f5
        createdAt:
          type: string
          format: date-time
          description: When the invitation was created
          example: '2025-09-01T14:30:00Z'
        claimedAt:
          type: string
          format: date-time
          description: When the invitation was claimed if it has been claimed
          example: '2025-09-01T14:30:00Z'
        url:
          type: string
          description: The URL where this invitation can be claimed.
          example: https://uma.me/i/019542f5
        expiresAt:
          type: string
          format: date-time
          description: When the invitation expires (if at all)
          example: '2025-09-01T14:30:00Z'
        inviterUma:
          type: string
          description: The UMA address of the inviter
          example: $inviter@uma.domain
        inviteeUma:
          type: string
          description: The UMA address of the invitee
          example: $invitee@uma.domain
        status:
          type: string
          enum:
            - PENDING
            - CLAIMED
            - EXPIRED
            - CANCELLED
          description: The status of the invitation
          example: PENDING
        firstName:
          type: string
          description: The inviter's first name. Will be displayed when the recipient clicks the invite link
          example: Jane
        amountToSend:
          $ref: '#/components/schemas/CurrencyAmount'
          description: |-
            The amount to send to the invitee when the invitation is claimed. This is optional and if not provided, the invitee will not receive any amount. Note that the actual sending of the amount must be done by the inviter platform once the INVITATION_CLAIMED webhook is received. If the inviter platform either does not send the payment or the payment fails, the invitee will not receive this amount. This field is primarily used for display purposes on the claiming side of the invitation.
            This field is useful for "send-by-link" style customer flows where an inviter can send a payment simply by sharing a link without knowing the receiver's UMA address. Note that these sends can only be sender-locked, meaning that the sender will not know ahead of time how much the receiver will receive in the receiving currency.
    UmaInvitationClaimRequest:
      type: object
      required:
        - inviteeUma
      properties:
        inviteeUma:
          type: string
          description: The UMA address of the customer claiming the invitation
          example: $invitee@uma.domain
    SandboxSendRequest:
      type: object
      required:
        - quoteId
        - currencyCode
      properties:
        quoteId:
          type: string
          description: The unique identifier of the quote
          example: Quote:019542f5-b3e7-1d02-0000-000000000006
        currencyCode:
          type: string
          description: Currency code for the funds to be sent
          example: USD
        currencyAmount:
          type: integer
          format: int64
          description: The amount to send in the smallest unit of the currency (eg. cents). If not provided, the amount will be derived from the quote.
          exclusiveMinimum: 0
          example: 1000
    SandboxUmaReceiveRequest:
      type: object
      required:
        - senderUmaAddress
        - receivingCurrencyCode
        - receivingCurrencyAmount
      properties:
        senderUmaAddress:
          type: string
          description: UMA address of the sender from the sandbox
          example: $success.usd@sandbox.grid.uma.money
        receiverUmaAddress:
          type: string
          description: UMA address of the receiver (optional if customerId is provided)
          example: $receiver@uma.domain
        customerId:
          type: string
          description: System ID of the receiver (optional if receiverUmaAddress is provided)
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        receivingCurrencyCode:
          type: string
          description: The currency code for the receiving amount
          example: USD
        receivingCurrencyAmount:
          type: integer
          format: int64
          description: The amount to be received in the smallest unit of the currency (eg. cents)
          exclusiveMinimum: 0
          example: 1000
    SandboxFundRequest:
      type: object
      required:
        - amount
      properties:
        amount:
          type: integer
          format: int64
          description: Amount to add in the smallest unit of the account's currency (e.g., cents for USD/EUR, satoshis for BTC)
          exclusiveMinimum: 0
          maximum: 100000000000
          example: 100000
        remittanceInformation:
          type: string
          maxLength: 80
          pattern: ^[ -~]*$
          description: |-
            Simulates the free-form payment information a depositor's bank sends with the funds. Depending on the payment rail, a real sender attaches it as the ACH Addenda record, the FedNow or RTP remittanceInformation field, or the wire OBI (Originator to Beneficiary Information). Only printable ASCII characters are allowed.
            It is reported on the resulting transaction: the incoming transaction for the credit, or the swept transaction for a `RULE_BASED` account. When it is set, the credit's `source` is a `REALTIME_FUNDING` ACH deposit from a placeholder sandbox depositor.
            A production ACH deposit reports this text as `source.railDetails.addenda`. Sandbox credits do not include `railDetails` yet, so the value appears only in the deprecated `source.remittanceInformation`.
          example: INV-2026-0042
        description:
          type: string
          maxLength: 1024
          description: |-
            Optional description/memo for the simulated credit, reported as the `description` of the incoming transaction. Defaults to a generated description when omitted.
            Not supported for `RULE_BASED` accounts: the swept transaction's description comes from the account's sweep rule, so sending it returns `400 INVALID_INPUT`.
          example: Test deposit for invoice reconciliation
    Permission:
      type: string
      enum:
        - VIEW
        - TRANSACT
        - MANAGE
      description: 'Permission of an API token that determines what actions the token can perform: VIEW: Can view all data, including platform config, customers and transactions TRANSACT: Can send payments MANAGE: Can manage platform config, api tokens and customers'
    ApiToken:
      type: object
      required:
        - id
        - name
        - permissions
        - clientId
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: System-generated unique identifier
          example: Token:019542f5-b3e7-1d02-0000-000000000001
        name:
          type: string
          description: Name of the token
          example: Sandbox read-only token
        permissions:
          type: array
          description: A list of permissions granted to the token
          items:
            $ref: '#/components/schemas/Permission'
        clientId:
          type: string
          description: An opaque identifier that should be used as a client_id (or username)  in the HTTP Basic Authentication scheme when issuing http requests to Grid.
          example: 01947d2284054f890000e63bca4810df
        clientSecret:
          type: string
          description: The secret that should be used to authenticate against Grid API. This secret is not stored and will never be available again after creation. Platform must keep this secret secure as it grants access to the account.
          example: ed0ad25881e234cc28fb2dec0a4fe64e4172
        expiresAt:
          type: string
          format: date-time
          description: Timestamp after which the token no longer authenticates. Requests made with an expired token return `401`. Omitted when the token does not expire.
          example: '2026-07-21T17:32:28Z'
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp
          example: '2025-07-21T17:32:28Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp
          example: '2025-07-21T17:32:28Z'
    TokenListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of tokens matching the filter criteria
          items:
            $ref: '#/components/schemas/ApiToken'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of tokens matching the criteria (excluding pagination)
    ApiTokenCreateRequest:
      type: object
      required:
        - name
        - permissions
      properties:
        name:
          type: string
          description: Name of the token to help identify it
          example: Sandbox read-only
        permissions:
          type: array
          description: A list of permissions to grant to the token
          items:
            $ref: '#/components/schemas/Permission'
    InternalAccountUpdateRequest:
      title: Internal Account Update Request
      description: Partial request body for `PATCH /internal-accounts/{id}`. At least one update field must be provided. On step 1 of the signed-retry flow Grid binds the submitted update fields into `payloadToSign`; on step 2 the client echoes the same fields back and Grid applies the update to the internal account.
      type: object
      properties:
        privateEnabled:
          type: boolean
          description: Whether wallet privacy should be enabled for the Embedded Wallet.
          example: true
    InternalAccountExportRequest:
      title: Internal Account Export Request
      description: Request body for `POST /internal-accounts/{id}/export`. The `clientPublicKey` is required on both steps of the signed-retry flow. On step 1 Grid binds it into `payloadToSign` so the subsequent stamp in `Grid-Wallet-Signature` commits to the target pubkey; on step 2 the client echoes the same `clientPublicKey` back and Grid uses it to encrypt the wallet credentials returned in the `200` response.
      type: object
      required:
        - clientPublicKey
      properties:
        clientPublicKey:
          type: string
          description: Fresh P-256 public key, uncompressed SEC1 hex — 130 hex chars where the first two are `04` (the uncompressed-point indicator). Generate a new keypair for each export and discard the private key after decrypting the response.
          example: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2
    InternalAccountExportResponse:
      title: Internal Account Export Response
      type: object
      required:
        - id
        - encryptedWalletCredentials
      properties:
        id:
          type: string
          description: The id of the internal account that was exported.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        encryptedWalletCredentials:
          type: string
          description: |-
            Encrypted wallet mnemonic, sealed to the `clientPublicKey` from the request body using HPKE: DHKEM(P-256, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM. Decrypt with the matching private key, then manage the mnemonic securely because it is the master key of the self-custodial Embedded Wallet.
            The value is a JSON string of the form `{"version": "v1.0.0", "data": "<hex>", "dataSignature": "<hex>", "enclaveQuorumPublic": "<hex>"}`. `data` hex-decodes to JSON `{"encappedPublic": "<hex>", "ciphertext": "<hex>", "organizationId": "<id>"}`, where `encappedPublic` is the uncompressed SEC1 ephemeral public key. `dataSignature` is an ECDSA-P256-SHA256 signature over the `data` bytes produced by the issuer key in `enclaveQuorumPublic`; verify before decrypting.
            In sandbox, `dataSignature` and `enclaveQuorumPublic` are empty strings. Clients should bypass attestation verification when calling against sandbox.
          example: '{"version":"v1.0.0","data":"7b22656e6361707065645075626c6963223a22303433...","dataSignature":"3045022100c9...","enclaveQuorumPublic":"04a1b2c3..."}'
    InternalAccountExportResult:
      title: Internal Account Export Result
      description: 'Response body for the signed retry of `POST /internal-accounts/{internalAccountId}/export`. Normally an `InternalAccountExportResponse` — the encrypted wallet credentials. When the underlying wallet-provider activity is still in flight, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"`. The credentials are never stored server-side, so re-sending the byte-identical signed retry is how they are collected: the re-send returns them as soon as the activity has settled, and the challenge is consumed only by the attempt that delivers them.'
      anyOf:
        - $ref: '#/components/schemas/InternalAccountExportResponse'
        - $ref: '#/components/schemas/WalletOperationProcessing'
    BalanceChange:
      type: object
      description: 'One movement of the account''s balance, signed in the account holder''s polarity: money out is negative and money in is positive, so a window''s changes sum to its closing balance less its opening balance.'
      required:
        - id
        - amount
        - effectiveAt
      properties:
        id:
          type: string
          description: Stable identifier for this balance change
          example: BalanceChange:019542f5-b3e7-1d02-0000-000000000003
        transactionId:
          type: string
          description: The transaction this change belongs to. Fetch it with `GET /transactions/{transactionId}` for its type, counterparty, merchant and rail. Several changes can share one transaction, for example an ACH deposit and its return, or a card purchase that settles in parts. Absent for an adjustment with no transaction behind it.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000004
        amount:
          $ref: '#/components/schemas/CurrencyAmount'
        effectiveAt:
          type: string
          format: date-time
          description: When the account holder's balance moved. Changes are ordered by this time.
          example: '2026-09-03T14:31:00Z'
    BalanceChangeListResponse:
      type: object
      description: A window of balance changes with the balances that bound it. The opening and closing balances describe the whole window, not the page, so they are the same on every page, and the identity `openingBalance + Σ(data[].amount) == closingBalance` holds only once every page's `data` is summed.
      required:
        - data
        - startDate
        - endDate
        - openingBalance
        - closingBalance
        - hasMore
      properties:
        data:
          type: array
          description: Balance changes on this page, ordered by `effectiveAt`
          items:
            $ref: '#/components/schemas/BalanceChange'
        startDate:
          type: string
          format: date-time
          description: Start of the window, inclusive
          example: '2026-08-01T00:00:00-05:00'
        endDate:
          type: string
          format: date-time
          description: End of the window, exclusive
          example: '2026-09-01T00:00:00-05:00'
        openingBalance:
          $ref: '#/components/schemas/CurrencyAmount'
        closingBalance:
          $ref: '#/components/schemas/CurrencyAmount'
        hasMore:
          type: boolean
          description: Indicates if more changes are available beyond this page
          example: false
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
          example: BalanceChange:019542f5-b3e7-1d02-0000-000000000003
        totalCount:
          type: integer
          description: Number of balance changes in the window, across all pages
          example: 42
    ConfirmStatementDeliveryRequest:
      type: object
      required:
        - statementMonth
        - statementDeliveredAt
      properties:
        statementMonth:
          type: string
          pattern: ^\d{4}-(0[1-9]|1[0-2])$
          description: The statement month the receipt covers, as `YYYY-MM`. A statement month is a calendar month in US Central time, and it must have already begun.
          example: 2026-09
        statementDeliveredAt:
          type: string
          format: date-time
          description: When you issued the statement to the customer. Must include a timezone offset, must not be in the future, and must be at or after the end of the period.
          example: '2026-10-03T14:31:00Z'
    StatementDelivery:
      type: object
      required:
        - internalAccountId
        - statementMonth
        - statementDeliveredAt
      properties:
        internalAccountId:
          type: string
          description: The account whose statement this is
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        statementMonth:
          type: string
          pattern: ^\d{4}-(0[1-9]|1[0-2])$
          description: The statement month, as `YYYY-MM`
          example: 2026-09
        fetchedAt:
          type: string
          format: date-time
          description: When the statement for this period was first read from Grid. Absent if it never has been
          example: '2026-10-01T09:00:00Z'
        statementDeliveredAt:
          type: string
          format: date-time
          description: When the statement was issued, from the first receipt for this period. Later receipts do not move it.
          example: '2026-10-03T14:31:00Z'
    AuthMethodType:
      type: string
      enum:
        - OAUTH
        - EMAIL_OTP
        - SMS_OTP
        - PASSKEY
      description: |-
        The type of authentication credential.
        - `OAUTH`: OpenID Connect (OIDC) token issued by an identity provider such as Google or Apple.
        - `EMAIL_OTP`: A one-time password delivered to the user's email address.
        - `SMS_OTP`: A one-time password delivered to the user's phone number.
        - `PASSKEY`: A WebAuthn passkey bound to the user's device.
    AuthMethod:
      type: object
      required:
        - id
        - accountId
        - type
        - nickname
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: System-generated unique identifier for the authentication credential.
          example: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
        accountId:
          type: string
          description: Identifier of the internal account that this credential authenticates.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        type:
          $ref: '#/components/schemas/AuthMethodType'
        credentialId:
          type: string
          description: Base64url-encoded WebAuthn credential identifier for this passkey. Present only for `PASSKEY` authentication credentials. Corresponds to `PublicKeyCredential.rawId`; pass this value as `allowCredentials[].id` when requesting a passkey assertion for this auth method.
          example: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
        nickname:
          type: string
          description: Human-readable identifier for this credential. For EMAIL_OTP credentials this is the email address; for SMS_OTP credentials this is the E.164 phone number; for OAUTH credentials it is typically the email claim from the OIDC token; for PASSKEY credentials it is the validated nickname provided at registration time.
          example: example@lightspark.com
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp.
          example: '2026-04-08T15:30:01Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp.
          example: '2026-04-08T15:35:00Z'
    AuthCredentialListResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          description: List of authentication credentials registered on the internal account.
          items:
            $ref: '#/components/schemas/AuthMethod'
    AuthCredentialCreateRequest:
      type: object
      required:
        - type
        - accountId
      properties:
        type:
          $ref: '#/components/schemas/AuthMethodType'
        accountId:
          type: string
          description: Identifier of the internal account that this credential will authenticate.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
    EmailOtpCredentialCreateRequestFields:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - EMAIL_OTP
          description: Discriminator value identifying this as an email OTP credential.
        email:
          type: string
          format: email
          maxLength: 255
          description: A new email address to register as a replacement credential.
          example: jane.new@example.com
    EmailOtpCredentialCreateRequest:
      title: Email OTP Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/EmailOtpCredentialCreateRequestFields'
    SmsOtpCredentialCreateRequestFields:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - SMS_OTP
          description: Discriminator value identifying this as an SMS OTP credential.
        phoneNumber:
          type: string
          pattern: ^\+[1-9]\d{1,14}$
          description: A new phone number, in strict E.164 format, to register as a replacement credential.
          example: '+14155559876'
    SmsOtpCredentialCreateRequest:
      title: SMS OTP Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/SmsOtpCredentialCreateRequestFields'
    OauthCredentialCreateRequestFields:
      type: object
      required:
        - type
        - oidcToken
      properties:
        type:
          type: string
          enum:
            - OAUTH
          description: Discriminator value identifying this as an OAuth credential.
        oidcToken:
          type: string
          description: OIDC ID token issued by the identity provider (e.g. Google, Apple). The token's `iss`, `aud`, and `sub` claims define the OAuth identity registered to this credential. In production, the provider signature is verified against the issuer's JWKS. In sandbox, the token must still be JWT-shaped with supported `iss`, non-empty `aud` and `sub`, numeric `iat` and `exp`, and `iat` less than 60 seconds before the request timestamp, but the signature segment may be a dummy value.
          example: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q
    OauthCredentialCreateRequest:
      title: OAuth Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/OauthCredentialCreateRequestFields'
    PasskeyAttestation:
      title: Passkey Attestation
      type: object
      required:
        - credentialId
        - clientDataJson
        - attestationObject
      properties:
        credentialId:
          type: string
          description: Base64url-encoded credential identifier produced by the authenticator at registration time. Typically the base64url of `PublicKeyCredential.rawId`.
          example: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY
        clientDataJson:
          type: string
          description: 'Base64url-encoded JSON client data collected by the browser during the WebAuthn `navigator.credentials.create()` call. Corresponds to `AuthenticatorAttestationResponse.clientDataJSON` from the WebAuthn spec — Grid''s field name is intentionally camelCased as `clientDataJson` (lowercase JSON) for consistency with the rest of the API; the value is the same bytes the browser returns. Contains the challenge, origin, and `type: "webauthn.create"`.'
          example: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0
        attestationObject:
          type: string
          description: Base64url-encoded CBOR attestation object produced by the authenticator during registration. Corresponds to `AuthenticatorAttestationResponse.attestationObject`.
          example: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow
        transports:
          type: array
          items:
            type: string
            enum:
              - usb
              - nfc
              - ble
              - internal
              - hybrid
          description: Optional. WebAuthn transports as returned by `AuthenticatorAttestationResponse.getTransports()`. Values follow the W3C `AuthenticatorTransport` enum — pass the raw values through to Grid; provider-specific translation is handled server-side. Some authenticators return an empty array; omit the field or send `[]` in that case.
          example:
            - internal
            - hybrid
    PasskeyCredentialCreateRequestFields:
      type: object
      required:
        - type
        - nickname
        - challenge
        - attestation
      properties:
        type:
          type: string
          enum:
            - PASSKEY
          description: Discriminator value identifying this as a passkey credential.
        nickname:
          type: string
          description: 'Human-readable identifier for the passkey, chosen by the user at registration time (e.g. "iPhone Face-ID", "YubiKey 5C"). Leading and trailing whitespace is ignored. Must be 1-100 characters and may contain Unicode letters, numbers, spaces, and the following separators: period, underscore, hyphen, apostrophe, and parentheses. Shown back on AuthMethod responses and in credential listings.'
          example: iPhone Face-ID
        challenge:
          type: string
          description: Base64url-encoded WebAuthn challenge issued by the platform backend and passed to the client before `navigator.credentials.create()`. Grid verifies it matches the challenge embedded in the attestation's `clientDataJson`, binding the attestation to this registration. Must be single-use.
          example: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx
        attestation:
          $ref: '#/components/schemas/PasskeyAttestation'
    PasskeyCredentialCreateRequest:
      title: Passkey Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/PasskeyCredentialCreateRequestFields'
    AuthCredentialCreateRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/EmailOtpCredentialCreateRequest'
        - $ref: '#/components/schemas/SmsOtpCredentialCreateRequest'
        - $ref: '#/components/schemas/OauthCredentialCreateRequest'
        - $ref: '#/components/schemas/PasskeyCredentialCreateRequest'
      discriminator:
        propertyName: type
        mapping:
          EMAIL_OTP: '#/components/schemas/EmailOtpCredentialCreateRequest'
          SMS_OTP: '#/components/schemas/SmsOtpCredentialCreateRequest'
          OAUTH: '#/components/schemas/OauthCredentialCreateRequest'
          PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest'
    AuthMethodResponse:
      title: Auth Method Response
      description: |-
        Strict wrapper around `AuthMethod`. Used directly as the registration response on `POST /auth/credentials` and inside `AuthCredentialResponseOneOf` for the `EMAIL_OTP` / `SMS_OTP` branches of `POST /auth/credentials/{id}/challenge`. The only difference from `AuthMethod` is `unevaluatedProperties: false`, which disambiguates the oneOf against `PasskeyAuthChallenge` — without the strictness, an `AuthMethod` with extra fields would ambiguously match both branches.

        For `EMAIL_OTP` and `SMS_OTP` credentials, responses that initiate or reissue an OTP challenge carry `otpEncryptionTargetBundle` so the client can HPKE-encrypt the OTP code in the subsequent `POST /auth/credentials/{id}/verify` call without the plaintext code ever transiting the server. First-time EMAIL_OTP wallet bootstrap registration can omit it; call `POST /auth/credentials/{id}/challenge` if it is absent.
      allOf:
        - $ref: '#/components/schemas/AuthMethod'
        - type: object
          properties:
            otpEncryptionTargetBundle:
              type: string
              description: HPKE encryption target bundle for a freshly initiated OTP challenge. Returned only on `EMAIL_OTP` and `SMS_OTP` responses that initiate or reissue an OTP challenge, such as `POST /auth/credentials/{id}/challenge` and signed-retry add responses. It is omitted from first-time EMAIL_OTP wallet bootstrap registration; call `POST /auth/credentials/{id}/challenge` for the new credential if it is absent. The client generates an ephemeral P-256 keypair (the Target Encryption Key, or TEK) and uses this bundle as the recipient when HPKE-encrypting `{otp_code, public_key}`, where `public_key` is the compressed TEK public key; the encrypted payload is submitted as `encryptedOtpBundle` on `POST /auth/credentials/{id}/verify`. The bundle is one-time-use per OTP issuance — re-issue via `POST /auth/credentials/{id}/challenge` to obtain a fresh bundle. The matching TEK private key must remain on the client and is used to stamp the exact UTF-8 bytes of the `payloadToSign` string returned by the first verify call. Preserve that string unchanged on the signed retry. Treat the bundle as opaque and pass it to your HPKE library; the Global Accounts client-keys guide shows how.
              example: '{"version":"v1.0.0","data":"7b227461726765745075626c6963...","dataSignature":"30450221...","enclaveQuorumPublic":"04a1b2c3..."}'
      unevaluatedProperties: false
    AuthSignedRequestChallenge:
      title: Authentication Signed Request Challenge
      description: |-
        202 response returned from Embedded Wallet Auth endpoints that require a signed retry — `POST /auth/credentials` (adding an additional credential), `DELETE /auth/credentials/{id}` (revoking a credential), `DELETE /auth/sessions/{id}` (revoking a session), and the `EMAIL_OTP` / `SMS_OTP` branch of `POST /auth/credentials/{id}/verify` (the secure OTP login flow, where the client submits an `encryptedOtpBundle` and receives a login signing message as `payloadToSign` to stamp for session issuance). Carries the signing fields from `SignedRequestChallenge` plus the `type` of the authentication credential involved (being added, revoked, that issued the session being revoked, or being authenticated). The client already knows the target resource id from the request path / body it just sent, so nothing beyond `type` is echoed in the response.

        The keypair used to compute the stamp depends on the operation. For credential / session management retries, sign with the session API keypair of an existing verified credential on the same internal account. For OTP verify retries, sign with the ephemeral Target Encryption Key (TEK) the client generated for this login — its public key is the one carried inside the `encryptedOtpBundle` and bound into the `verificationToken`, and it becomes the client's session API key on successful completion.
      allOf:
        - $ref: '#/components/schemas/SignedRequestChallenge'
        - type: object
          required:
            - type
          properties:
            type:
              $ref: '#/components/schemas/AuthMethodType'
              description: 'Credential type relevant to this challenge: the credential type being added (`POST /auth/credentials`), revoked (`DELETE /auth/credentials/{id}`), or authenticated (`EMAIL_OTP` / `SMS_OTP` branch of `POST /auth/credentials/{id}/verify`). For session revocation, this is the type of credential that issued the session (`DELETE /auth/sessions/{id}`).'
    AuthCredentialVerifyRequest:
      type: object
      required:
        - type
      properties:
        type:
          $ref: '#/components/schemas/AuthMethodType'
    EmailOtpCredentialVerifyRequestFields:
      type: object
      required:
        - type
        - encryptedOtpBundle
      description: Verify an email-OTP credential via the secure two-leg flow. The client HPKE-encrypts the OTP code (together with its public key) under the `otpEncryptionTargetBundle` returned from registration when present, or from `POST /auth/credentials/{id}/challenge` when registration omitted it or the OTP must be reissued, submits the result here, and receives `202` with a `payloadToSign` containing a login signing message bound to the client's public key. The client stamps the exact UTF-8 bytes of that string, unchanged, with the matching TEK private key and retries this request with `Grid-Wallet-Signature` + `Request-Id` headers to obtain the session. Plaintext OTP codes are never sent over the wire.
      properties:
        type:
          type: string
          enum:
            - EMAIL_OTP
          description: Discriminator value identifying this as an email OTP verification.
        encryptedOtpBundle:
          type: string
          description: |-
            HPKE-sealed OTP attempt — the OTP code never reaches Grid in plaintext. The client generates a fresh ephemeral P-256 key pair (the session signing key pair it keeps once login completes), HPKE-encrypts `{otp_code, public_key}` (the code the user entered plus that key pair's public key) to the key in `otpEncryptionTargetBundle`, and submits the encrypted result here. `public_key` must be the compressed public key: 66 hex characters starting with `02` or `03`. In production, a bundle with an uncompressed key fails with `400 INVALID_INPUT`, even when the OTP code is correct. A wrong OTP code returns the same error. The `verificationToken` is bound to this key, so the signed retry's `Grid-Wallet-Signature` stamp must carry the same compressed `publicKey`. The value is the `{encappedPublic, ciphertext}` JSON an HPKE library produces; the Global Accounts client-keys guide has a worked example.

            On success the response is `202` with a `payloadToSign` containing a login signing message bound to the public key sealed in this bundle. Build an API-key stamp over the exact UTF-8 bytes of that string with the matching TEK private key, without parsing, re-serializing, or trimming it, then retry this request with the full stamp in `Grid-Wallet-Signature` and the `requestId` in `Request-Id` to complete the flow and receive the session. The client keeps that private key as the session signing key, and its public key becomes the session API key.
          example: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}'
    EmailOtpCredentialVerifyRequest:
      title: Email OTP Credential Verify Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialVerifyRequest'
        - $ref: '#/components/schemas/EmailOtpCredentialVerifyRequestFields'
    SmsOtpCredentialVerifyRequestFields:
      type: object
      required:
        - type
        - encryptedOtpBundle
      description: Verify an SMS-OTP credential via the same secure two-leg flow as email OTP. The client HPKE-encrypts the OTP code (together with its public key) under the `otpEncryptionTargetBundle` returned from registration or `POST /auth/credentials/{id}/challenge`, submits the result here, and receives `202` with a `payloadToSign` containing a login signing message bound to the client's public key. The client stamps the exact UTF-8 bytes of that string, unchanged, with the matching TEK private key and retries this request with `Grid-Wallet-Signature` + `Request-Id` headers to obtain the session. Plaintext OTP codes are never sent over the wire.
      properties:
        type:
          type: string
          enum:
            - SMS_OTP
          description: Discriminator value identifying this as an SMS OTP verification.
        encryptedOtpBundle:
          type: string
          description: HPKE-sealed OTP attempt. Same format and retry semantics as `EmailOtpCredentialVerifyRequest.encryptedOtpBundle`.
          example: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}'
    SmsOtpCredentialVerifyRequest:
      title: SMS OTP Credential Verify Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialVerifyRequest'
        - $ref: '#/components/schemas/SmsOtpCredentialVerifyRequestFields'
    OauthCredentialVerifyRequestFields:
      type: object
      required:
        - type
        - oidcToken
        - clientPublicKey
      properties:
        type:
          type: string
          enum:
            - OAUTH
          description: Discriminator value identifying this as an OAuth verification.
        oidcToken:
          type: string
          description: OIDC ID token issued by the identity provider. For reauthentication after a prior session expired, supply a fresh token — the token's `iat` claim must be less than 60 seconds before the request timestamp. The token identity (`iss`, `aud`, and `sub`) must match the registered OAuth credential. In production, the provider signature is verified against the issuer's JWKS. In sandbox, the token must still be JWT-shaped with supported `iss`, non-empty `aud` and `sub`, numeric `iat` and `exp`, and a `nonce` equal to `sha256(clientPublicKey)`, but the signature segment may be a dummy value.
          example: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q
        clientPublicKey:
          type: string
          description: Client-generated, ephemeral P-256 public key; the matching private key is retained on the client and is one-time-use per verification request. Send a compressed SEC1 key (`02`/`03` prefix followed by the 32-byte X coordinate; 66 hex characters) for the recommended client-held-key model, where that private key becomes the session signing key. Send an uncompressed SEC1 key (`04` prefix followed by the 32-byte X and 32-byte Y coordinates; 130 hex characters) for the deprecated legacy flow, where Grid seals the session signing key to it instead.
          example: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31
    OauthCredentialVerifyRequest:
      title: OAuth Credential Verify Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialVerifyRequest'
        - $ref: '#/components/schemas/OauthCredentialVerifyRequestFields'
    PasskeyAssertion:
      title: Passkey Assertion
      type: object
      description: WebAuthn assertion returned by `navigator.credentials.get()`. In sandbox, Grid validates the assertion against the registered passkey credential so the client-side flow can match production. In production, Grid validates the WebAuthn assertion.
      required:
        - credentialId
        - clientDataJson
        - authenticatorData
        - signature
      properties:
        credentialId:
          type: string
          description: Base64url-encoded credential identifier returned during the WebAuthn assertion. Corresponds to `PublicKeyCredential.rawId`.
          example: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
        clientDataJson:
          type: string
          description: 'Base64url-encoded JSON client data collected by the browser during the WebAuthn `navigator.credentials.get()` call. Corresponds to `AuthenticatorAssertionResponse.clientDataJSON` from the WebAuthn spec — Grid''s field name is intentionally camelCased as `clientDataJson` (lowercase JSON) for consistency with the rest of the API; the value is the same bytes the browser returns. Contains the challenge, origin, and `type: "webauthn.get"`.'
          example: eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0
        authenticatorData:
          type: string
          description: Base64url-encoded authenticator data returned by the authenticator during the assertion. Corresponds to `AuthenticatorAssertionResponse.authenticatorData`.
          example: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA
        signature:
          type: string
          description: Base64url-encoded signature produced by the authenticator over `authenticatorData || SHA-256(clientDataJSON)`. Corresponds to `AuthenticatorAssertionResponse.signature`. The signature byte format is determined by the credential's public-key algorithm — DER-encoded ECDSA for ES256 (P-256, typical for passkeys), PKCS#1 v1.5 for RS256, or a raw 64-byte signature for EdDSA.
          example: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900
        userHandle:
          type: string
          description: Base64url-encoded user handle returned by the authenticator. Corresponds to `AuthenticatorAssertionResponse.userHandle`. Populated (and required by the WebAuthn spec) for discoverable credentials — resident keys used in the "Sign in with passkey" autofill flow — and typically present for passkey registrations. Omit this field entirely for non-discoverable credentials specified via `allowCredentials` where the authenticator returns no user handle.
          example: dXNlci1oYW5kbGUtZXhhbXBsZQ
    PasskeyCredentialVerifyRequestFields:
      type: object
      required:
        - type
        - assertion
      properties:
        type:
          type: string
          enum:
            - PASSKEY
          description: Discriminator value identifying this as a passkey verification.
        assertion:
          $ref: '#/components/schemas/PasskeyAssertion'
    PasskeyCredentialVerifyRequest:
      title: Passkey Credential Verify Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialVerifyRequest'
        - $ref: '#/components/schemas/PasskeyCredentialVerifyRequestFields'
    AuthCredentialVerifyRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/EmailOtpCredentialVerifyRequest'
        - $ref: '#/components/schemas/SmsOtpCredentialVerifyRequest'
        - $ref: '#/components/schemas/OauthCredentialVerifyRequest'
        - $ref: '#/components/schemas/PasskeyCredentialVerifyRequest'
      discriminator:
        propertyName: type
        mapping:
          EMAIL_OTP: '#/components/schemas/EmailOtpCredentialVerifyRequest'
          SMS_OTP: '#/components/schemas/SmsOtpCredentialVerifyRequest'
          OAUTH: '#/components/schemas/OauthCredentialVerifyRequest'
          PASSKEY: '#/components/schemas/PasskeyCredentialVerifyRequest'
    AuthSession:
      title: Authentication Session
      description: 'An authentication session on an Embedded Wallet internal account. Returned from `GET /auth/sessions` (list) and `POST /auth/credentials/{id}/verify` (on credential verification) or `POST /auth/sessions/{id}/refresh` (on mid-session refresh). The `clientPublicKey` encoding on the issuing request selects the flow: a compressed key gets the client-held-key model, where the client generates and retains the session signing key and session-issuing responses carry no key material; an uncompressed key gets the deprecated legacy flow, where Grid seals the session signing key to that public key and returns it as `encryptedSessionSigningKey`. Never returned by the list endpoint.'
      allOf:
        - $ref: '#/components/schemas/AuthMethod'
        - type: object
          required:
            - id
            - expiresAt
          properties:
            id:
              type: string
              description: System-generated unique identifier for the session. Pass this value to `DELETE /auth/sessions/{id}` to revoke the session before `expiresAt`. Overrides the `id` inherited from `AuthMethod` so this response identifies the session rather than the authenticating credential.
              example: Session:019542f5-b3e7-1d02-0000-000000000003
            encryptedSessionSigningKey:
              type: string
              deprecated: true
              description: |-
                Deprecated; present only for the legacy flow, selected by sending an uncompressed `clientPublicKey` on the verification or refresh request. Grid seals the session signing key to that public key and returns it here as a base58check string (a 33-byte compressed P-256 encapsulated public key followed by AES-256-GCM ciphertext) for the client to decrypt with its private key.

                The recommended client-held-key flow sends a compressed `clientPublicKey` instead: the client generates and retains the session signing key itself, so this field is omitted — the same way `EMAIL_OTP` and `SMS_OTP` sessions have always worked. See the "Client keys & signing" guide. Always omitted from list responses (`GET /auth/sessions`).
              example: w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf
            expiresAt:
              type: string
              format: date-time
              description: Timestamp after which the session is no longer valid and the session signing key must not be used to sign further requests.
              example: '2026-04-09T15:30:01Z'
    AuthCredentialVerifyResponse:
      title: Auth Credential Verify Response
      description: 'Response body for `POST /auth/credentials/{id}/verify`. Normally an `AuthSession` — the issued session on successful verification. When the underlying wallet-provider activity is still in flight, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical request until it gets the session; the backend also reconciles the activity to terminal on its own.'
      anyOf:
        - $ref: '#/components/schemas/AuthSession'
        - $ref: '#/components/schemas/WalletOperationProcessing'
    AuthCredentialChallengeRequest:
      title: Auth Credential Challenge Request
      description: Request body for `POST /auth/credentials/{id}/challenge`. Required when re-challenging a `PASSKEY` credential — must carry `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from. Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient because the OTP is delivered out-of-band. OAuth credentials do not use this endpoint; authenticate or reauthenticate them with `POST /auth/credentials/{id}/verify`.
      type: object
      properties:
        clientPublicKey:
          type: string
          pattern: ^(0[23][0-9a-fA-F]{64}|04[0-9a-fA-F]{128})$
          minLength: 66
          maxLength: 130
          description: Required for `PASSKEY` credentials; the matching private key is retained on the client. Send a compressed SEC1 key (`02`/`03` prefix followed by the 32-byte X coordinate; 66 hex characters) for the recommended client-held-key model, where that private key becomes the session signing key. Send an uncompressed SEC1 key (`04` prefix followed by the 32-byte X and 32-byte Y coordinates; 130 hex characters) for the deprecated legacy flow, where Grid seals the session signing key to it instead. Grid bakes this public key into the session-creation payload that the returned `challenge` is computed from. Ignored for `EMAIL_OTP` and `SMS_OTP`.
          example: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31
    PasskeyAuthChallenge:
      title: Passkey Auth Challenge
      description: Extended `AuthMethod` shape returned for `PASSKEY` credentials from `POST /auth/credentials/{id}/challenge`. Includes the WebAuthn `credentialId` needed to target the passkey, plus the Grid-issued `challenge`, corresponding `requestId`, and challenge `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical session-creation request body, not a base64url string. The client UTF-8 encodes this string as the WebAuthn challenge and signs it with the passkey to produce the assertion submitted to `POST /auth/credentials/{id}/verify`.
      allOf:
        - $ref: '#/components/schemas/AuthMethod'
        - type: object
          required:
            - credentialId
            - challenge
            - requestId
            - expiresAt
          properties:
            credentialId:
              type: string
              description: Base64url-encoded WebAuthn credential identifier for this passkey. Corresponds to `PublicKeyCredential.rawId`; pass this value as `allowCredentials[].id` when requesting a passkey assertion for this auth method.
              example: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
            challenge:
              type: string
              description: Lowercase hex-encoded SHA-256 digest of the canonical session-creation request body for the pending passkey authentication. Do not base64url-decode this field; pass UTF-8 bytes of the string (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge to `navigator.credentials.get()`. Single-use; a new challenge is issued on the next call to `POST /auth/credentials/{id}/challenge`.
              example: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729
            requestId:
              type: string
              description: Grid-issued `Request:<uuid>` identifier for this pending passkey authentication request. Echo this value exactly as the `Request-Id` header on the subsequent `POST /auth/credentials/{id}/verify` call so Grid can correlate the assertion with the issued challenge.
              example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
            expiresAt:
              type: string
              format: date-time
              description: Timestamp after which the issued challenge is no longer valid. The assertion must reach `POST /auth/credentials/{id}/verify` before this time; otherwise the client must request a fresh challenge via `POST /auth/credentials/{id}/challenge`.
              example: '2026-04-08T15:35:00Z'
    AuthCredentialResponseOneOf:
      title: Auth Credential Response
      description: Discriminated response shape returned from `POST /auth/credentials/{id}/challenge`. For `EMAIL_OTP` and `SMS_OTP` credentials the body is a plain `AuthMethod` (wrapped as `AuthMethodResponse` to disambiguate the oneOf). For `PASSKEY` credentials the body is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, Grid-issued `challenge`, `requestId`, and `expiresAt` that drive the subsequent assertion. OAuth credentials do not use the challenge endpoint. Registration responses from `POST /auth/credentials` use the simpler `AuthMethodResponse` shape directly for all credential types.
      oneOf:
        - $ref: '#/components/schemas/AuthMethodResponse'
        - $ref: '#/components/schemas/PasskeyAuthChallenge'
      discriminator:
        propertyName: type
        mapping:
          EMAIL_OTP: '#/components/schemas/AuthMethodResponse'
          SMS_OTP: '#/components/schemas/AuthMethodResponse'
          PASSKEY: '#/components/schemas/PasskeyAuthChallenge'
    AuthCredentialChallengeResponse:
      title: Auth Credential Challenge Response
      description: 'Response body for `POST /auth/credentials/{id}/challenge`. Normally an `AuthCredentialResponseOneOf` — the re-issued challenge or re-sent OTP. When the OTP send''s underlying wallet-provider activity is still in flight, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — re-request the challenge until the send settles; the backend also reconciles it to terminal on its own.'
      anyOf:
        - $ref: '#/components/schemas/AuthCredentialResponseOneOf'
        - $ref: '#/components/schemas/WalletOperationProcessing'
    SessionListResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          description: List of active authentication sessions for the internal account.
          items:
            $ref: '#/components/schemas/AuthSession'
    AuthSessionRefreshRequest:
      title: Auth Session Refresh Request
      description: 'Request body for refreshing an active authentication session. The `clientPublicKey` is required on both steps of the signed-retry flow and must match on both. Its SEC1 encoding selects how the refreshed session signing key is delivered: a compressed key gets the recommended client-held-key model, where the client retains the new signing key itself; an uncompressed key gets the deprecated legacy flow, where Grid returns the new key as `encryptedSessionSigningKey` sealed to it. On the initial call, Grid binds the supplied key into the session-creation payload returned as `payloadToSign`.'
      type: object
      required:
        - clientPublicKey
      properties:
        clientPublicKey:
          type: string
          pattern: ^(0[23][0-9a-fA-F]{64}|04[0-9a-fA-F]{128})$
          description: Client-generated P-256 public key; the matching private key is retained on the client. Send a compressed SEC1 key (`02`/`03` prefix followed by the 32-byte X coordinate; 66 hex characters) for the recommended client-held-key model, where that private key becomes the new session signing key. Send an uncompressed SEC1 key (`04` prefix followed by the 32-byte X and 32-byte Y coordinates; 130 hex characters) for the deprecated legacy flow, where Grid seals the new session signing key to it and returns it as `encryptedSessionSigningKey` on the signed retry.
          example: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31
    DelegatedKeyStatus:
      type: string
      enum:
        - PENDING
        - ACTIVE
        - REVOKED
      description: |-
        Status of a delegated signing key.

        - `PENDING`: The delegated user exists but the policy-creation leg never completed. The key cannot sign.
        - `ACTIVE`: The policy is granted and the key may stamp quote executions.
        - `REVOKED`: The delegated user has been deleted and the key can no longer sign.
      example: ACTIVE
    DelegatedKey:
      title: Delegated Key
      type: object
      required:
        - id
        - cardId
        - fundingSourceId
        - accountId
        - publicKey
        - nickname
        - status
        - createdAt
        - updatedAt
      description: A delegated signing key for a card funding source backed by an Embedded Wallet internal account. Returned from `POST /auth/delegated-keys` (on activation), `GET /auth/delegated-keys` (list), and `GET /auth/delegated-keys/{id}`. The keypair is generated and custodied by Grid; the private key is never returned. While `ACTIVE`, Grid may use the key to authorize Spark token-transaction signing for the card funding source's Embedded Wallet funding account in place of a session keypair. `publicKey` is informational metadata identifying the credential.
      properties:
        id:
          type: string
          description: Grid-issued `DelegatedKey:<uuid>` identifier.
          example: DelegatedKey:019542f5-b3e7-1d02-0000-000000000021
        cardId:
          type: string
          description: The card this key is delegated for.
          example: Card:019542f5-b3e7-1d02-0000-000000000010
        fundingSourceId:
          type: string
          description: The card funding source this key is delegated for.
          example: CardFundingSource:019542f5-b3e7-1d02-0000-000000000011
        accountId:
          type: string
          description: The Embedded Wallet internal account this key is delegated for, derived from the card funding source.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        publicKey:
          type: string
          description: Compressed P-256 public key (hex) of the delegated API keypair.
          example: 02a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90
        nickname:
          type: string
          description: Human-readable label for the delegated key.
          example: Settlement service key
        status:
          $ref: '#/components/schemas/DelegatedKeyStatus'
        createdAt:
          type: string
          format: date-time
          description: When the delegated key was created.
          example: '2026-04-08T15:30:01Z'
        updatedAt:
          type: string
          format: date-time
          description: When the delegated key was last updated.
          example: '2026-04-08T15:30:42Z'
    DelegatedKeyListResponse:
      title: Delegated Key List Response
      type: object
      required:
        - data
      properties:
        data:
          type: array
          description: Delegated signing keys matching the list filters.
          items:
            $ref: '#/components/schemas/DelegatedKey'
    DelegatedKeyCreateRequest:
      title: Delegated Key Create Request
      type: object
      required:
        - cardId
        - internalAccountId
        - nickname
      properties:
        cardId:
          type: string
          description: The id of the card whose Embedded Wallet funding account will use this delegated signing key.
          example: Card:019542f5-b3e7-1d02-0000-000000000010
        internalAccountId:
          type: string
          description: The id of the Embedded Wallet internal account funding the card. Grid uses the `(cardId, internalAccountId)` pair to find the active card funding-source binding.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        nickname:
          type: string
          minLength: 1
          maxLength: 256
          description: Human-readable label for the delegated key.
          example: Card payments key
    DelegatedKeySignedRequestChallenge:
      title: Delegated Key Signed Request Challenge
      description: 202 response returned from the delegated-key endpoints. Stamp `payloadToSign` with the session API keypair of a verified credential on the delegated key's Embedded Wallet funding account, then retry the same request with the full stamp in `Grid-Wallet-Signature` and the `requestId` echoed in `Request-Id`.
      allOf:
        - $ref: '#/components/schemas/SignedRequestChallenge'
    AgentPermission:
      type: string
      enum:
        - VIEW_TRANSACTIONS
        - CREATE_QUOTES
        - EXECUTE_QUOTES
        - MANAGE_EXTERNAL_ACCOUNTS
      description: 'Permission granted to an agent that determines what actions it can perform. VIEW_TRANSACTIONS: Can list and retrieve transactions and account balances. CREATE_QUOTES: Can create quotes. EXECUTE_QUOTES: Can execute quotes. MANAGE_EXTERNAL_ACCOUNTS: Can create and manage external accounts.'
    AgentExecutionMode:
      type: string
      enum:
        - AUTO
        - APPROVAL_REQUIRED
      description: 'Execution mode controlling whether agent actions require human approval. AUTO: The agent can execute actions autonomously without explicit approval. APPROVAL_REQUIRED: All agent actions require explicit human approval before execution.'
    AgentSpendingLimits:
      type: object
      description: Spending limits that cap the agent's transaction amounts and frequency. All amount fields are integers in the smallest unit of the specified currency. When a transaction is denominated in a different currency, Grid converts using the exchange rate at evaluation time.
      required:
        - currency
        - perTransactionLimit
      properties:
        currency:
          type: string
          description: ISO 4217 currency code that all amount limits are denominated in.
          example: USD
        perTransactionLimit:
          type: integer
          description: Maximum amount the agent can transfer in a single transaction.
          example: 50000
        dailyLimit:
          type:
            - integer
            - 'null'
          description: Maximum total amount the agent can transfer per day. Null means no daily limit.
          example: 500000
        dailyTransactionLimit:
          type: integer
          description: Maximum number of transactions the agent can initiate per day.
          example: 10
        monthlyLimit:
          type:
            - integer
            - 'null'
          description: Maximum total amount the agent can transfer per month. Null means no monthly limit.
          example: 5000000
    AgentAccountRule:
      type: object
      description: Per-account policy override that takes precedence over the agent's default policy for a specific account.
      required:
        - accountId
      properties:
        accountId:
          type: string
          description: The internal account ID this rule applies to.
          example: Account:019542f5-b3e7-1d02-0000-000000000001
        executionMode:
          $ref: '#/components/schemas/AgentExecutionMode'
        perTransactionLimit:
          type:
            - integer
            - 'null'
          description: Per-transaction limit override, in the smallest unit of the relevant currency. Null inherits from the agent's spending limits.
          example: 10000
    AgentAccountRestrictions:
      type: object
      description: Optional restrictions that limit the agent to specific accounts or override policy per account.
      properties:
        allowedAccountIds:
          type:
            - array
            - 'null'
          description: If set, restricts the agent to operate only on the specified internal account IDs. Null means the agent can access all accounts.
          items:
            type: string
            example: Account:019542f5-b3e7-1d02-0000-000000000001
        accountRules:
          type: array
          description: Per-account rules that override the agent's default policy for specific accounts.
          items:
            $ref: '#/components/schemas/AgentAccountRule'
    AgentApprovalThresholds:
      type: object
      description: Thresholds that force approval for high-value transactions, overriding the default execution mode. When a transaction is denominated in a different currency than the threshold, Grid converts using the exchange rate at evaluation time.
      properties:
        currency:
          type: string
          description: ISO 4217 currency code that the amount threshold is denominated in. Required when amount is set.
          example: USD
        amount:
          type:
            - integer
            - 'null'
          description: If set, any transaction above this amount (in the smallest unit of the specified currency) will require explicit approval even when the agent's defaultExecutionMode is AUTO. Null means no threshold override.
          example: 100000
    AgentPolicy:
      type: object
      description: Policy governing what an agent can do, how it executes actions, and its spending boundaries.
      required:
        - permissions
        - defaultExecutionMode
        - spendingLimits
      properties:
        permissions:
          type: array
          description: List of permissions granted to the agent.
          items:
            $ref: '#/components/schemas/AgentPermission'
        defaultExecutionMode:
          $ref: '#/components/schemas/AgentExecutionMode'
        spendingLimits:
          $ref: '#/components/schemas/AgentSpendingLimits'
        accountRestrictions:
          $ref: '#/components/schemas/AgentAccountRestrictions'
        approvalThresholds:
          $ref: '#/components/schemas/AgentApprovalThresholds'
    AgentUsage:
      type: object
      description: Real-time counters tracking the agent's spending and transaction activity against its policy limits.
      required:
        - dailyTransactionCount
        - dailySpend
        - monthlySpend
      properties:
        dailyTransactionCount:
          type: integer
          description: Number of transactions initiated by the agent today.
          example: 3
        dailySpend:
          type: integer
          description: Total amount spent by the agent today, in the smallest unit of the policy's `spendingLimits.currency`.
          example: 150000
        dailyResetDate:
          type: string
          format: date
          description: The date when daily usage counters will reset.
          example: '2025-07-22'
        monthlySpend:
          type: integer
          description: Total amount spent by the agent this month, in the smallest unit of the policy's `spendingLimits.currency`.
          example: 750000
        monthlyResetMonth:
          type: string
          description: The year-month (YYYY-MM) when monthly usage counters will reset.
          example: 2025-08
    Agent:
      type: object
      description: A programmatic agent with scoped permissions and a spending policy, used to automate payment workflows.
      required:
        - id
        - name
        - customerId
        - isPaused
        - isConnected
        - policy
        - usage
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: System-generated unique identifier for the agent.
          example: Agent:019542f5-b3e7-1d02-0000-000000000001
        name:
          type: string
          description: Human-readable name for the agent.
          example: Payroll Automation Agent
        customerId:
          type: string
          description: The ID of the customer this agent operates on behalf of.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        isPaused:
          type: boolean
          description: Whether the agent is currently paused. Paused agents cannot initiate any actions.
          example: false
        isConnected:
          type: boolean
          description: Whether the agent has been installed and connected (i.e., its device code has been redeemed).
          example: true
        policy:
          $ref: '#/components/schemas/AgentPolicy'
        usage:
          $ref: '#/components/schemas/AgentUsage'
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp.
          example: '2025-07-21T17:32:28Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp.
          example: '2025-07-21T17:32:28Z'
    AgentListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of agents matching the filter criteria.
          items:
            $ref: '#/components/schemas/Agent'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page.
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true).
        totalCount:
          type: integer
          description: Total number of agents matching the criteria (excluding pagination).
    AgentCreateRequest:
      type: object
      required:
        - name
        - customerId
        - policy
      properties:
        name:
          type: string
          description: Human-readable name to identify the agent.
          example: Payroll Automation Agent
        customerId:
          type: string
          description: The ID of the customer this agent will operate on behalf of.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        policy:
          $ref: '#/components/schemas/AgentPolicy'
    AgentDeviceCode:
      type: object
      required:
        - code
        - agentId
        - expiresAt
        - redeemed
      properties:
        code:
          type: string
          description: Human-readable device code used to install and connect the agent software.
          example: GRID-AGENT-X7K9-M2P4
        agentId:
          type: string
          description: The agent this device code belongs to.
          example: Agent:019542f5-b3e7-1d02-0000-000000000001
        expiresAt:
          type: string
          format: date-time
          description: Timestamp when this device code expires.
          example: '2025-07-22T17:32:28Z'
        redeemed:
          type: boolean
          description: Whether this device code has already been redeemed by the agent.
          example: false
    AgentCreateResponse:
      type: object
      description: Response returned when an agent is created, including the agent and a device code for installation.
      required:
        - agent
        - deviceCode
      properties:
        agent:
          $ref: '#/components/schemas/Agent'
        deviceCode:
          $ref: '#/components/schemas/AgentDeviceCode'
    AgentActionStatus:
      type: string
      enum:
        - PENDING_APPROVAL
        - APPROVED
        - REJECTED
        - FAILED
      description: |
        Status of an agent action.

        | Status | Description |
        |--------|-------------|
        | `PENDING_APPROVAL` | Submitted by the agent, awaiting platform approval before execution |
        | `APPROVED` | Approved by the platform; execution is in progress or completed |
        | `REJECTED` | Rejected by the platform; the underlying transaction was not executed |
        | `FAILED` | Approved but execution failed (e.g. quote expired, insufficient funds) |
    AgentActionType:
      type: string
      enum:
        - EXECUTE_QUOTE
      description: |
        The type of action the agent is requesting.

        | Type | Description |
        |------|-------------|
        | `EXECUTE_QUOTE` | Execute a quote |
    AgentAction:
      type: object
      description: An action submitted by an agent that may require platform approval before execution. All agent-initiated operations (quote execution, transfers) are represented as AgentActions, giving the platform a consistent object to approve, reject, and audit regardless of the underlying operation type.
      required:
        - id
        - agentId
        - customerId
        - platformCustomerId
        - status
        - type
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: System-generated unique identifier for this action.
          example: AgentAction:019542f5-b3e7-1d02-0000-000000000099
        agentId:
          type: string
          description: The agent that submitted this action.
          example: Agent:019542f5-b3e7-1d02-0000-000000000042
        customerId:
          type: string
          description: The customer on whose behalf the action was submitted.
          example: Customer:019542f5-b3e7-1d02-0000-000000000010
        platformCustomerId:
          type: string
          description: Platform-specific ID of the customer.
          example: user-a1b2c3
        status:
          $ref: '#/components/schemas/AgentActionStatus'
        type:
          $ref: '#/components/schemas/AgentActionType'
        quote:
          allOf:
            - $ref: '#/components/schemas/Quote'
          description: The quote being executed. Contains the full amount, currency, destination, and rate details needed to present an approval decision to the user.
        transaction:
          allOf:
            - $ref: '#/components/schemas/TransactionOneOf'
          description: The resulting transaction, populated once the action has been approved and execution has begun. Absent while the action is `PENDING_APPROVAL` or `REJECTED`.
        rejectionReason:
          type: string
          description: Human-readable reason provided by the platform when rejecting the action. Only present when status is `REJECTED`.
          example: Transaction amount exceeds customer's current risk limit.
        createdAt:
          type: string
          format: date-time
          description: When the action was submitted by the agent.
          example: '2025-10-03T15:00:00Z'
        updatedAt:
          type: string
          format: date-time
          description: When the action was last updated.
          example: '2025-10-03T15:02:00Z'
    AgentActionListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of agent actions matching the filter criteria.
          items:
            $ref: '#/components/schemas/AgentAction'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page.
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true).
        totalCount:
          type: integer
          description: Total number of actions matching the criteria (excluding pagination).
    AgentUpdateRequest:
      type: object
      description: Partial update to an agent's basic fields. At least one field must be provided.
      properties:
        name:
          type: string
          description: Updated name for the agent.
          example: Updated Payroll Agent
        isPaused:
          type: boolean
          description: Set to true to pause the agent or false to resume it.
          example: true
    AgentSpendingLimitsUpdate:
      type: object
      description: Partial update to spending limits. Only provided fields will be updated; omitted fields retain their current values.
      properties:
        currency:
          type: string
          description: ISO 4217 currency code that all amount limits are denominated in. Updating this recasts all existing limits into the new currency denomination.
          example: USD
        perTransactionLimit:
          type: integer
          description: Maximum amount per transaction.
          example: 50000
        dailyLimit:
          type:
            - integer
            - 'null'
          description: Maximum daily spend. Set to null to remove the daily limit.
          example: 500000
        dailyTransactionLimit:
          type: integer
          description: Maximum number of transactions per day.
          example: 10
        monthlyLimit:
          type:
            - integer
            - 'null'
          description: Maximum monthly spend. Set to null to remove the monthly limit.
          example: 5000000
    AgentPolicyUpdateRequest:
      type: object
      description: Partial update to an agent's policy. Only provided fields will be updated; omitted fields retain their current values.
      properties:
        permissions:
          type: array
          description: Updated list of permissions. Replaces the entire permissions list when provided.
          items:
            $ref: '#/components/schemas/AgentPermission'
        defaultExecutionMode:
          $ref: '#/components/schemas/AgentExecutionMode'
        spendingLimits:
          $ref: '#/components/schemas/AgentSpendingLimitsUpdate'
        accountRestrictions:
          $ref: '#/components/schemas/AgentAccountRestrictions'
        approvalThresholds:
          $ref: '#/components/schemas/AgentApprovalThresholds'
    AgentActionRejectRequest:
      type: object
      properties:
        reason:
          type: string
          description: Optional human-readable reason for the rejection, stored on the action and visible to the platform.
          example: Transaction amount exceeds customer's current risk limit.
    AgentDeviceCodeStatusResponse:
      type: object
      required:
        - code
        - redeemed
      properties:
        code:
          type: string
          description: The device code.
          example: GRID-AGENT-X7K9-M2P4
        redeemed:
          type: boolean
          description: Whether this device code has been redeemed.
          example: false
    AgentDeviceCodeRedeemResponse:
      type: object
      required:
        - agentId
        - agentName
        - accessToken
        - policy
      properties:
        agentId:
          type: string
          description: The agent's system-generated ID.
          example: Agent:019542f5-b3e7-1d02-0000-000000000001
        agentName:
          type: string
          description: The agent's name.
          example: Payroll Automation Agent
        accessToken:
          type: string
          description: 'Bearer token used to authenticate all subsequent API calls as this agent. Pass as `Authorization: Bearer <accessToken>`. This token is returned only once and must be stored securely — it cannot be retrieved again.'
          example: gat_ed0ad25881e234cc28fb2dec0a4fe64e4172a3b9
        policy:
          $ref: '#/components/schemas/AgentPolicy'
    ExternalAccountCreateRequest:
      allOf:
        - type: object
          required:
            - currency
            - accountInfo
          properties:
            customerId:
              type: string
              description: The ID of the customer for whom to create the external account. If not provided, the external account will be created on behalf of the platform.
              example: Customer:019542f5-b3e7-1d02-0000-000000000001
            currency:
              type: string
              description: The ISO 4217 currency code
              example: USD
            platformAccountId:
              type: string
              maxLength: 255
              description: Your platform's identifier for the account in your system. This can be used to reference the account by your own identifier.
              example: ext_acc_123456
            ownershipType:
              $ref: '#/components/schemas/OwnershipType'
            defaultUmaDepositAccount:
              type: boolean
              description: Whether to set the external account as the default UMA deposit account. When set to true, incoming payments to this customer's UMA address will be automatically deposited into this external account. False if not provided. Note that only one external account can be set as the default UMA deposit account for a customer, so if there is already a default UMA deposit account, this will override the existing default UMA deposit account. If there is no default UMA deposit account, incoming UMA payments will be deposited into the primary internal account for the customer.
              default: false
            accountInfo:
              $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf'
    CardStatus:
      type: string
      enum:
        - PENDING_KYC
        - PROCESSING
        - PENDING_AUTH
        - ACTIVE
        - FROZEN
        - CLOSED
      description: |
        Lifecycle status of a card.

        | Status | Description |
        |-------|-------------|
        | `PENDING_KYC` | The cardholder has not yet completed KYC. Cards in this status cannot transact. |
        | `PROCESSING` | The card has been requested and is being provisioned with the issuer. |
        | `PENDING_AUTH` | The issuer has provisioned the card, but its USDB Embedded Wallet funding source lacks an active delegated signing key. Complete `POST /auth/delegated-keys` before using the card. |
        | `ACTIVE` | The card is live and can authorize transactions. |
        | `FROZEN` | The card is temporarily disabled by the platform. New authorizations are declined with `cardDeclinedReason: CARD_NOT_ACTIVE`. Existing settlements and refunds continue to reconcile. |
        | `CLOSED` | The card is permanently closed. Terminal, irreversible status. |
    CardStatusReason:
      type: string
      enum:
        - DELEGATION_REQUIRED
        - ISSUER_REJECTED
        - CLOSED_BY_PLATFORM
        - CLOSED_BY_GRID
      description: |
        Reason a card reached a terminal or non-active status. Present on
        `PENDING_AUTH` and `CLOSED` cards, and on cards that fail provisioning before reaching
        `ACTIVE`.

        | Reason | Description |
        |--------|-------------|
        | `DELEGATION_REQUIRED` | The card funding source has no usable delegated signing key. Present while the card is `PENDING_AUTH`. |
        | `ISSUER_REJECTED` | The card issuer rejected provisioning during `PROCESSING`. |
        | `CLOSED_BY_PLATFORM` | The card was closed via `PATCH /cards/{id}` (`status: CLOSED`) by the platform. |
        | `CLOSED_BY_GRID` | The card was closed by Grid (e.g. compliance or risk action). |
    CardBrand:
      type: string
      enum:
        - VISA
        - MASTERCARD
      description: |
        Card network brand. Read-only — determined by Grid when the card is
        provisioned with the issuer.
    CardForm:
      type: string
      enum:
        - VIRTUAL
      description: |
        Physical form factor of the card. Only `VIRTUAL` is supported in v1;
        `PHYSICAL` will be added in a later release.
    CardPinStatus:
      type: string
      enum:
        - NOT_SET
        - OK
        - BLOCKED
      example: OK
      description: |
        State of the card's PIN. On the Card resource, this is the last known status
        and is present whenever PIN management is supported. An absent value means
        PIN management is unavailable for this card; it does not mean `NOT_SET`.
        Use `GET /cards/{id}` to retrieve `pinStatus`.

        | Status | Description |
        |--------|-------------|
        | `NOT_SET` | No PIN has been set on the card. PIN verification is not enabled. Set one with `POST /cards/{id}/set-pin` or the hosted form returned by `GET /cards/{id}/pin-entry-url`. |
        | `OK` | A PIN is set and usable. |
        | `BLOCKED` | The PIN was entered incorrectly three consecutive times and is refused until cleared with `POST /cards/{id}/pin/unblock`, or replaced with `POST /cards/{id}/set-pin`. |
    Card:
      type: object
      required:
        - id
        - customerId
        - status
        - form
        - fundingSource
        - maxSpendPerTransaction
        - maxSpendPerDay
        - maxTransactionsPerDay
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: System-generated unique card identifier
          example: Card:019542f5-b3e7-1d02-0000-000000000010
        customerId:
          type: string
          description: The id of the `Customer` who holds this card.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        platformCardId:
          type: string
          description: Platform-specific card identifier generated by the server.
          example: card-emp-001
        status:
          $ref: '#/components/schemas/CardStatus'
        statusReason:
          $ref: '#/components/schemas/CardStatusReason'
          description: Reason associated with the current `status`. Present when the card is `PENDING_AUTH`, `CLOSED`, or when provisioning was rejected; absent otherwise.
        brand:
          $ref: '#/components/schemas/CardBrand'
        form:
          $ref: '#/components/schemas/CardForm'
        pinStatus:
          $ref: '#/components/schemas/CardPinStatus'
        pinUnblockAvailableAt:
          type: string
          format: date-time
          readOnly: true
          description: Earliest time another PIN unblock attempt is allowed. Absent until the unblock allowance is used. Changing the PIN does not reset this time.
          example: '2026-10-15T15:00:00Z'
        last4:
          type: string
          description: Last four digits of the card PAN.
          example: '4242'
        expMonth:
          type: integer
          minimum: 1
          maximum: 12
          description: Card expiration month (1–12).
          example: 12
        expYear:
          type: integer
          description: Card expiration year (four digits).
          example: 2029
        fundingSource:
          type: string
          description: Internal account id that funds this card.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        cardCapabilities:
          $ref: '#/components/schemas/CardCapabilities'
          description: Actions supported for this card by the issuer selected at issuance. Present for cards whose program has been resolved; absent otherwise. These capabilities are fixed at issuance for the card's lifetime.
        maxSpendPerTransaction:
          anyOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: 'null'
          description: The largest amount this card can authorize on a single transaction, in the smallest unit of its `currency` (cents for USD). An authorization for exactly the limit is allowed. A later clearing can still settle above it — a restaurant tip, for example — so this caps the authorization, not the final settled amount. `null` means the card sets no limit of its own. If your platform config also sets `cardConfigs.maxSpendPerTransaction` (the platform-level limit), the lower of the two applies and this value stays as you set it.
          example: 5000
        maxSpendPerDay:
          anyOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: 'null'
          description: Card-specific cap on cumulative new spend during one UTC calendar day, in the smallest unit of the card's `currency`. The window resets at 00:00 UTC. Null means the card has no card-specific daily cap. When the platform config also supplies `cardConfigs.maxSpendPerDay`, Grid enforces the lower of the two values without replacing this configured value. Refunds, reversals, and authorization expiries do not restore capacity during the day. Spend exactly equal to the effective limit is allowed.
          example: 25000
        maxTransactionsPerDay:
          anyOf:
            - type: integer
              format: int32
              minimum: 1
              maximum: 2147483647
            - type: 'null'
          description: Card-specific cap on the number of transactions the card may authorize during one UTC calendar day. The window resets at 00:00 UTC. Null means the card has no card-specific daily transaction cap. When the platform config also supplies `cardConfigs.maxTransactionsPerDay`, Grid enforces the lower of the two values without replacing this configured value. Each approved authorization counts once for the day it was authorized; refunds, reversals, and authorization expiries do not restore capacity during the day. A transaction that brings the day's count exactly to the effective limit is allowed.
          example: 20
        currency:
          type: string
          description: Currency the card transacts in, fixed at issuance by its card program. USDB-funded cards transact in USD, with funding converted at 1 USDB = 1 USD. Spending limits use the smallest unit of the card's currency (USD cents for USDB-funded cards). Changing the funding source does not change the card's currency or spending-limit units.
          example: USD
        processorRef:
          type: string
          description: Opaque processor-side reference for the card (e.g. the Lithic card token). Useful for cross-referencing in the processor's dashboards; not used for any Grid request routing.
          example: card_b81c2a4f
        issuerRef:
          type: string
          description: Opaque identifier for the card on the issuer of record (e.g. the Lead Bank account/card identifier). Useful for cross-referencing in issuer dashboards; not used for any Grid request routing.
          example: lead_card_7a1b9c3d
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp
          example: '2026-05-08T14:10:00Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp
          example: '2026-05-08T14:11:00Z'
    CardListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          description: List of cards matching the filter criteria
          items:
            $ref: '#/components/schemas/Card'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results (only present if hasMore is true)
        totalCount:
          type: integer
          description: Total number of cards matching the criteria (excluding pagination)
    CardCreateRequest:
      type: object
      required:
        - customerId
        - form
        - fundingSource
      properties:
        customerId:
          type: string
          description: The id of the `Customer` to issue the card to. The customer must have KYC status `APPROVED`; otherwise the request is rejected with `CARDHOLDER_KYC_NOT_APPROVED`.
          example: Customer:019542f5-b3e7-1d02-0000-000000000001
        platformCardId:
          type: string
          description: Platform-specific card identifier. Always generated by the server; any value supplied in the request is ignored.
          example: card-emp-001
        threeDSecurePassword:
          type: string
          description: 'Static password used as the card''s 3-D Secure factor. Required when the first funding-source internal account''s `cardCapabilities.supports3dSecurePassword` is true; omitting it or supplying an empty or whitespace-only string is rejected with `INVALID_INPUT`. When the capability is false, supplying this field is rejected with `INVALID_INPUT` because cards in that program have no static-password factor. Grid does not retain the value: it is forwarded to the issuer and discarded, so it cannot be read back afterwards; a cardholder who forgets it must set a new one through `PATCH /cards/{id}`.'
          example: AbCd1234EfGh5678
        form:
          $ref: '#/components/schemas/CardForm'
        fundingSource:
          type: string
          description: Internal account id that funds this card. The account must belong to the customer and be denominated in a card-eligible currency; otherwise the request is rejected with `INVALID_INPUT`.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        maxSpendPerTransaction:
          type: integer
          format: int64
          minimum: 1
          maximum: 9007199254740991
          description: The largest amount this card can authorize on a single transaction, in the smallest unit of its currency (cents for USD). An authorization for exactly the limit is allowed. A later clearing can still settle above it — a restaurant tip, for example — so this caps the authorization, not the final settled amount. Omit the field to set no limit. If your platform config also sets `cardConfigs.maxSpendPerTransaction`, the lower of the two applies. You can only send this when the funding-source internal account's `cardCapabilities.supportsSpendLimitsAtIssuance` is true.
          example: 5000
        maxSpendPerDay:
          type: integer
          format: int64
          minimum: 1
          maximum: 9007199254740991
          description: Optional card-specific cap on cumulative new spend during one UTC calendar day, in the smallest unit of the card's currency (USD cents for USDB-funded cards). Omit this field for no card-specific daily cap. When the platform config also supplies `cardConfigs.maxSpendPerDay`, Grid enforces the lower of the two values. The window resets at 00:00 UTC, and refunds, reversals, and authorization expiries do not restore capacity during the day. Accepted only when the funding-source internal account's `cardCapabilities.supportsSpendLimitsAtIssuance` is true. Spend exactly equal to the effective limit is allowed.
          example: 25000
        maxTransactionsPerDay:
          type: integer
          format: int32
          minimum: 1
          maximum: 2147483647
          description: Optional card-specific cap on the number of transactions the card may authorize during one UTC calendar day. Omit this field for no card-specific daily transaction cap. When the platform config also supplies `cardConfigs.maxTransactionsPerDay`, Grid enforces the lower of the two values. The window resets at 00:00 UTC. Each approved authorization counts once; refunds, reversals, and authorization expiries do not restore capacity during the day. Accepted only when the funding-source internal account's `cardCapabilities.supportsTransactionCountLimit` is true.
          example: 20
    CardUpdateRequest:
      type: object
      description: Update request for `PATCH /cards/{id}`. At least one of `status`, `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, `maxTransactionsPerDay`, or `threeDSecurePassword` must be supplied. Supplying `status` also requires `substatus` and `reason`, so every card state change carries why it happened. `status` transitions include `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. Explicit activation requires completed delegated signing authorization; otherwise it returns `409 INVALID_STATE_TRANSITION`. Funding-source changes reconcile `ACTIVE` / `PENDING_AUTH` automatically. `PENDING_AUTH` cannot be requested. `CLOSED` is terminal and irreversible and cannot be combined with `fundingSource`, `maxSpendPerTransaction`, `maxSpendPerDay`, or `maxTransactionsPerDay`.
      properties:
        status:
          type: string
          enum:
            - ACTIVE
            - FROZEN
            - CLOSED
          description: Target status for the card. Permitted transitions are `ACTIVE ⇄ FROZEN`, `PENDING_AUTH → ACTIVE | FROZEN`, and `ACTIVE | PENDING_AUTH | FROZEN → CLOSED`. Activation requires completed delegated signing authorization. `CLOSED` is terminal and irreversible; once closed, the card stays in the system for audit and reconciliation but cannot transact again.
          example: FROZEN
        substatus:
          type: string
          enum:
            - LOST
            - COMPROMISED
            - DAMAGED
            - END_USER_REQUEST
            - ISSUER_REQUEST
            - NOT_ACTIVE
            - SUSPICIOUS_ACTIVITY
            - INTERNAL_REVIEW
            - EXPIRED
            - UNDELIVERABLE
            - OTHER
          description: Why the card is moving, in the card issuer's vocabulary. Required whenever `status` is supplied, and forwarded to the issuer. Pick `OTHER` when none of the named values fit and say why in `reason`.
          example: SUSPICIOUS_ACTIVITY
        reason:
          type: string
          minLength: 1
          description: A short sentence naming why the card is moving. Required whenever `status` is supplied, and recorded against the card so a later reader can tell why it changed.
          example: Cardholder reported the card stolen.
        fundingSource:
          type: string
          description: 'Replaces the card''s funding source. Must belong to the customer and be denominated in a currency supported by the card''s program, including USDB for USD cards. Changing the funding source does not change the card''s currency or spending-limit units. Cannot be supplied alongside `status: CLOSED`. To stop a card from spending, set `status: FROZEN` instead.'
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        maxSpendPerTransaction:
          anyOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: 'null'
          description: 'A new limit on the largest amount this card can authorize on a single transaction, in the smallest unit of its currency (cents for USD). An authorization for exactly the limit is allowed. A later clearing can still settle above it — a restaurant tip, for example — so this caps the authorization, not the final settled amount. Send a positive integer to set the limit, `null` to remove it, or omit the field to leave it unchanged. If your platform config also sets `cardConfigs.maxSpendPerTransaction`, the lower of the two applies. You can only send this when the card''s `cardCapabilities.supportsSpendLimits` is true, and not together with `status: CLOSED`.'
          example: 10000
        maxSpendPerDay:
          anyOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: 'null'
          description: 'Replacement card-specific UTC-calendar-day cap, in the smallest unit of the card''s currency. Omit this field to leave the current cap unchanged, supply null to clear it, or supply a positive integer to set it. When the platform config also supplies `cardConfigs.maxSpendPerDay`, Grid enforces the lower of the two values. Refunds, reversals, and authorization expiries do not restore capacity during the day. Accepted only when the card''s `cardCapabilities.supportsSpendLimits` is true. Cannot be supplied alongside `status: CLOSED`.'
          example: 25000
        maxTransactionsPerDay:
          anyOf:
            - type: integer
              format: int32
              minimum: 1
              maximum: 2147483647
            - type: 'null'
          description: 'Replacement card-specific cap on the number of transactions the card may authorize during one UTC calendar day. Omit this field to leave the current cap unchanged, supply null to clear it, or supply a positive integer to set it. When the platform config also supplies `cardConfigs.maxTransactionsPerDay`, Grid enforces the lower of the two values. Refunds, reversals, and authorization expiries do not restore capacity during the day. Accepted only when the card''s `cardCapabilities.supportsTransactionCountLimit` is true. Cannot be supplied alongside `status: CLOSED`.'
          example: 20
        threeDSecurePassword:
          type: string
          description: Sets a new static 3-D Secure password on the card. Send it on its own, and only when the card's `cardCapabilities.supports3dSecurePassword` is true.
          example: AbCd1234EfGh5678
    CardRevealRequest:
      type: object
      description: Optional overrides for one reveal. Omit the body entirely to use your platform's configured settings.
      properties:
        cssUrl:
          anyOf:
            - type: string
              format: uri
              pattern: ^https://[^@]*$
              maxLength: 512
            - type: 'null'
          description: 'HTTPS URL of a stylesheet for this reveal only, overriding `cardConfigs.panRevealCssUrl` for this call without changing your platform config. Use it to style one reveal differently from the rest — to match the cardholder''s light or dark theme, for example. Subject to the same rules as the platform-level setting: it must be reachable over HTTPS without credentials, it styles the same selectors, and it loads into the page rendering the live card details, so host it somewhere you would trust with them. Omit the field, or send `null`, to fall back to your platform config.'
          example: https://acme.com/card-reveal-dark.css
    CardRevealResponse:
      type: object
      required:
        - panEmbedUrl
        - expiresAt
      properties:
        panEmbedUrl:
          type: string
          format: uri
          description: 'Signed URL of the card processor''s iframe that securely displays the PAN, CVV, and expiry to the cardholder. The full PAN and CVV never cross Grid''s servers — render this URL in an iframe in your client to reveal card details. The URL is a short-lived bearer secret: render it immediately and never store, cache, or log it.'
          example: https://embed.lithic.com/iframe/...?t=...
        expiresAt:
          type: string
          format: date-time
          description: When the signed URL stops loading. Request a new reveal rather than re-rendering an expired URL.
          example: '2026-05-08T14:16:00Z'
    DigitalWallet:
      type: string
      enum:
        - APPLE_PAY
        - GOOGLE_PAY
        - SAMSUNG_PAY
      description: The digital wallet a card is being added to.
      example: APPLE_PAY
    CardTokenizationRequest:
      type: object
      description: Fields shared by every add-to-wallet request. The wallet SDK hands your app a set of values when the cardholder taps "Add to Wallet"; pass them here unchanged in the request shape for that `wallet`.
      required:
        - wallet
      properties:
        wallet:
          $ref: '#/components/schemas/DigitalWallet'
    ApplePayTokenizationRequestFields:
      type: object
      required:
        - wallet
        - certificate
        - nonce
        - nonceSignature
      properties:
        wallet:
          type: string
          enum:
            - APPLE_PAY
          description: Discriminator value identifying an Apple Pay request.
          example: APPLE_PAY
        certificate:
          type: string
          description: The leaf certificate from the `certificates` array Apple passes to your `PKAddPaymentPassViewControllerDelegate`, base64-encoded in PEM form.
          example: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM=
        nonce:
          type: string
          description: The `nonce` Apple passes to your delegate, base64-encoded.
          example: MTIzNDU2Nzg=
        nonceSignature:
          type: string
          description: The `nonceSignature` Apple passes to your delegate, base64-encoded.
          example: c2lnbmF0dXJl
    ApplePayTokenizationRequest:
      title: Apple Pay Tokenization Request
      allOf:
        - $ref: '#/components/schemas/CardTokenizationRequest'
        - $ref: '#/components/schemas/ApplePayTokenizationRequestFields'
    GooglePayTokenizationRequestFields:
      type: object
      required:
        - wallet
        - walletAccountId
        - serverSessionId
      properties:
        wallet:
          type: string
          enum:
            - GOOGLE_PAY
          description: Discriminator value identifying a Google Pay request.
          example: GOOGLE_PAY
        walletAccountId:
          type: string
          description: The stable identifier of the Google Wallet account the card is being added to, as reported by the TapAndPay client.
          example: wallet-account-8f14e45f
        serverSessionId:
          type: string
          format: uuid
          description: The `serverSessionId` Google supplies in the TapAndPay payment-credentials callback under Unified Push Provisioning.
          example: 8c3b8a7e-2a5d-4b7f-9c1e-6d2f0a1b3c4d
        deviceId:
          type: string
          description: The stable device identifier from `getStableHardwareId`, when the SDK supplies one. Unified Push Provisioning does not always.
          example: device-3c59dc04
        opaquePaymentCardRequested:
          type: boolean
          default: false
          description: Set to the value Google supplies in the payment-credentials callback. When `true`, the response also carries `googleAccountPayload`, which saves the card to the cardholder's Google Account for online payments in addition to the device token.
          example: false
    GooglePayTokenizationRequest:
      title: Google Pay Tokenization Request
      allOf:
        - $ref: '#/components/schemas/CardTokenizationRequest'
        - $ref: '#/components/schemas/GooglePayTokenizationRequestFields'
    SamsungPayTokenizationRequestFields:
      type: object
      required:
        - wallet
        - walletAccountId
        - deviceId
      properties:
        wallet:
          type: string
          enum:
            - SAMSUNG_PAY
          description: Discriminator value identifying a Samsung Pay request.
          example: SAMSUNG_PAY
        walletAccountId:
          type: string
          description: The cardholder's Samsung Wallet identifier (`walletUserId`) reported by the Samsung Pay SDK.
          example: wallet-account-8f14e45f
        deviceId:
          type: string
          description: The stable device identifier reported by the Samsung Pay SDK.
          example: device-3c59dc04
    SamsungPayTokenizationRequest:
      title: Samsung Pay Tokenization Request
      allOf:
        - $ref: '#/components/schemas/CardTokenizationRequest'
        - $ref: '#/components/schemas/SamsungPayTokenizationRequestFields'
    CardTokenizationRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/ApplePayTokenizationRequest'
        - $ref: '#/components/schemas/GooglePayTokenizationRequest'
        - $ref: '#/components/schemas/SamsungPayTokenizationRequest'
      discriminator:
        propertyName: wallet
        mapping:
          APPLE_PAY: '#/components/schemas/ApplePayTokenizationRequest'
          GOOGLE_PAY: '#/components/schemas/GooglePayTokenizationRequest'
          SAMSUNG_PAY: '#/components/schemas/SamsungPayTokenizationRequest'
    CardTokenizationResponse:
      type: object
      description: 'Fields shared by every add-to-wallet response. The payload is encrypted to the wallet provider''s keys: neither your app nor your servers can read the card data inside it. Hand it to the wallet SDK; it is valid for a single provisioning attempt.'
      required:
        - wallet
      properties:
        wallet:
          $ref: '#/components/schemas/DigitalWallet'
    CardTokenizationApplePayPayload:
      type: object
      description: The encrypted pass data for Apple Wallet. Pass the three fields straight into the `PKAddPaymentPassRequest` your delegate's completion handler expects.
      required:
        - activationData
        - encryptedPassData
        - ephemeralPublicKey
      properties:
        activationData:
          type: string
          description: Base64 `activationData` for `PKAddPaymentPassRequest`.
          example: YWN0aXZhdGlvbi1kYXRh
        encryptedPassData:
          type: string
          description: Base64 `encryptedPassData` for `PKAddPaymentPassRequest`.
          example: ZW5jcnlwdGVkLXBhc3MtZGF0YQ==
        ephemeralPublicKey:
          type: string
          description: Base64 `ephemeralPublicKey` for `PKAddPaymentPassRequest`.
          example: ZXBoZW1lcmFsLXB1YmxpYy1rZXk=
    ApplePayTokenizationResponseFields:
      type: object
      required:
        - wallet
        - applePay
      properties:
        wallet:
          type: string
          enum:
            - APPLE_PAY
          description: Discriminator value identifying an Apple Pay response.
          example: APPLE_PAY
        applePay:
          $ref: '#/components/schemas/CardTokenizationApplePayPayload'
    ApplePayTokenizationResponse:
      title: Apple Pay Tokenization Response
      allOf:
        - $ref: '#/components/schemas/CardTokenizationResponse'
        - $ref: '#/components/schemas/ApplePayTokenizationResponseFields'
    GooglePayTokenizationResponseFields:
      type: object
      required:
        - wallet
        - provisioningPayload
      properties:
        wallet:
          type: string
          enum:
            - GOOGLE_PAY
          description: Discriminator value identifying a Google Pay response.
          example: GOOGLE_PAY
        provisioningPayload:
          type: string
          description: Base64-encoded opaque payload for the device token. Return it from the TapAndPay payment-credentials callback.
          example: eyJjYXJkIjoiLi4uIn0=
        googleAccountPayload:
          type: string
          description: Present when `opaquePaymentCardRequested` was `true`. Return it alongside `provisioningPayload` so Google also saves the card to the cardholder's Google Account.
          example: eyJmcGFuIjoiLi4uIn0=
    GooglePayTokenizationResponse:
      title: Google Pay Tokenization Response
      allOf:
        - $ref: '#/components/schemas/CardTokenizationResponse'
        - $ref: '#/components/schemas/GooglePayTokenizationResponseFields'
    SamsungPayTokenizationResponseFields:
      type: object
      required:
        - wallet
        - provisioningPayload
      properties:
        wallet:
          type: string
          enum:
            - SAMSUNG_PAY
          description: Discriminator value identifying a Samsung Pay response.
          example: SAMSUNG_PAY
        provisioningPayload:
          type: string
          description: Base64-encoded opaque payload. Pass it to the Samsung Pay SDK's `addCard` call.
          example: eyJjYXJkIjoiLi4uIn0=
    SamsungPayTokenizationResponse:
      title: Samsung Pay Tokenization Response
      allOf:
        - $ref: '#/components/schemas/CardTokenizationResponse'
        - $ref: '#/components/schemas/SamsungPayTokenizationResponseFields'
    CardTokenizationResponseOneOf:
      oneOf:
        - $ref: '#/components/schemas/ApplePayTokenizationResponse'
        - $ref: '#/components/schemas/GooglePayTokenizationResponse'
        - $ref: '#/components/schemas/SamsungPayTokenizationResponse'
      discriminator:
        propertyName: wallet
        mapping:
          APPLE_PAY: '#/components/schemas/ApplePayTokenizationResponse'
          GOOGLE_PAY: '#/components/schemas/GooglePayTokenizationResponse'
          SAMSUNG_PAY: '#/components/schemas/SamsungPayTokenizationResponse'
    CardPinSetRequest:
      type: object
      required:
        - encryptedPinBlock
      properties:
        encryptedPinBlock:
          type: string
          description: |-
            The cardholder's PIN, encrypted in your client before calling Grid. Use the [PIN encryption public key](https://docs.lightspark.com/keys/pin-encryption-public-key.pem.txt) for both sandbox and production. Grid cannot decrypt the block and never stores or logs PIN material.

            Build it in the client, immediately after PIN entry, so the PIN never reaches your servers either: serialize `{"nonce": <random integer>, "pin": "<4-digit PIN>"}` as JSON, encode it as UTF-8, encrypt it using the public key above, and base64-encode the ciphertext. Generate a fresh cryptographically random integer `nonce` for each request to prevent replay. Preserve the PIN as a string, including leading zeros. Send the resulting base64 string as `encryptedPinBlock`. Never send the plaintext PIN or the unencrypted JSON to Grid. Keep ciphertext out of logs, storage, and analytics too.

            A rejected block returns `400 INVALID_INPUT`. Check the payload, encoding, and public key, then create a new block with a fresh nonce. See [Card PINs](https://docs.lightspark.com/cards/card-management/pins) for the complete PIN-entry flows.
          minLength: 16
          maxLength: 1024
          example: SGVsbG8sIHRoaXMgaXMgYSBiYXNlNjQtZW5jb2RlZCBjaXBoZXJ0ZXh0Li4u
    CardPinUrlResponse:
      type: object
      description: A temporary PIN-entry iframe URL, its credential, and expiration.
      required:
        - iframeUrl
        - sessionToken
        - expiresAt
        - environment
      properties:
        iframeUrl:
          type: string
          format: uri
          description: |-
            Ready-to-use URL for the secure PIN-entry iframe. Set this as your iframe's `src` without modifying it. The cardholder enters their PIN inside the iframe, so the plaintext PIN never reaches your servers or Grid's. Use the message flow in the [Card PIN guide](https://docs.lightspark.com/cards/card-management/pins) to submit and confirm the result.

            The URL contains a temporary credential. Never store, cache, or log it. It expires at `expiresAt` and permits one successful PIN submission.
          example: https://embed.example.com/pin?session=eyJhbGciOiJIUzI1NiJ9...
        sessionToken:
          type: string
          description: Temporary credential authorizing one successful PIN submission. It is already included in `iframeUrl`; use that URL to load the form. Never store, cache, or log the token. Use the iframe's submission result to confirm a change; status `OK` alone cannot prove that an existing PIN was changed. Read `GET /cards/{id}` for its `pinStatus`.
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
        expiresAt:
          type: string
          format: date-time
          description: When the URL stops working. Request a new URL rather than reusing an expired URL or token.
          example: '2026-05-08T14:16:00Z'
        environment:
          type: string
          enum:
            - SANDBOX
            - PRODUCTION
          description: Session environment. The iframeUrl already selects the matching environment.
          example: SANDBOX
    SandboxCardAuthorizationRequest:
      type: object
      required:
        - amount
        - currency
        - merchant
      description: 'Sandbox-only request body shared by the card authorization-family simulate endpoints: `simulate/authorization`, `simulate/credit_authorization`, `simulate/financial_authorization`, `simulate/financial_credit_authorization`, and `simulate/credit_authorization_advice`. Drives the same internal authorization + reconcile paths that the issuer would call in production. The decisioning outcome is controlled by the last three characters of `merchant.descriptor` — see the `simulate/authorization` documentation for the suffix table. `merchant.descriptor` must be 1–25 characters.'
      properties:
        amount:
          type: integer
          format: int64
          description: Authorization amount in the smallest unit of `currency` (e.g. cents for USD).
          exclusiveMinimum: 0
          example: 1250
        currency:
          $ref: '#/components/schemas/Currency'
        merchant:
          $ref: '#/components/schemas/CardMerchant'
    SandboxCardSimulationResponse:
      type: object
      required:
        - issuerTransactionToken
      description: Response body for the sandbox card-event simulators. The simulate call pokes the card issuer's sandbox; the resulting card operation is delivered asynchronously via the issuer's events webhook, never synchronously in this response.
      properties:
        issuerTransactionToken:
          type: string
          description: The card issuer's transaction token for the simulated event. Correlates the eventual webhook-delivered card operation back to this simulate call.
          example: f3a1c2d4-5b6e-7890-abcd-ef0123456789
    SandboxCardClearingRequest:
      type: object
      required:
        - cardTransactionId
        - amount
      description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/clearing`. Drives a clearing event against an existing `CardTransaction`. Pass an `amount` greater than the authorized amount to exercise the over-auth / restaurant-tip post-hoc-pull path; pass `0` to exercise `AUTHORIZATION_EXPIRY`. Suffix-driven outcomes on the parent transaction's id govern whether the post-hoc pull succeeds.
      properties:
        cardTransactionId:
          type: string
          description: The id of the `CardTransaction` to clear against. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
        amount:
          type: integer
          format: int64
          description: Clearing amount in the smallest unit of the transaction's currency. Set to `0` to simulate an authorization expiry with no clearing.
          minimum: 0
          example: 1500
    SandboxCardReturnRequest:
      type: object
      required:
        - cardTransactionId
        - amount
      description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/return`. Drives a `RETURN` event against an existing settled `CardTransaction`, which creates a `CardRefund` and posts the return as its own dated `CREDIT` `CardTransaction` linked to the purchase via `originalTransactionId`.
      properties:
        cardTransactionId:
          type: string
          description: The id of the `CardTransaction` to refund against. Must have at least one settled clearing.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
        amount:
          type: integer
          format: int64
          description: Return amount in the smallest unit of the transaction's currency. Must be less than or equal to the net settled amount (settled minus previously-refunded).
          exclusiveMinimum: 0
          example: 1500
    SandboxCardBalanceInquiryRequest:
      type: object
      required:
        - merchant
      description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/balance_inquiry`. Drives a balance-inquiry authorization against the card. A balance inquiry is always a `0`-amount authorization, so it carries no `amount`.
      properties:
        merchant:
          $ref: '#/components/schemas/CardMerchant'
    SandboxCardAuthorizationAdviceRequest:
      type: object
      required:
        - cardTransactionId
        - amount
      description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/authorization_advice`. Drives an `AUTHORIZATION_ADVICE` that re-sizes an existing open authorization.
      properties:
        cardTransactionId:
          type: string
          description: The id of the `CardTransaction` the advice re-sizes. Must be in `AUTHORIZED` or `PARTIALLY_SETTLED` state.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
        amount:
          type: integer
          format: int64
          description: The new *total* authorized amount, in the smallest unit of the transaction's currency, that the advice re-sizes the authorization to.
          exclusiveMinimum: 0
          example: 2000
    SandboxCardTransactionRefRequest:
      type: object
      required:
        - cardTransactionId
      description: 'Sandbox-only request body for simulate endpoints keyed only by an existing card transaction: `POST /sandbox/cards/{id}/simulate/return_reversal` and `POST /sandbox/cards/{id}/simulate/authorization_expiry`.'
      properties:
        cardTransactionId:
          type: string
          description: The id of the `CardTransaction` to act against.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
    SandboxCardReversalRequest:
      type: object
      required:
        - cardTransactionId
      description: 'Request body for `POST /sandbox/cards/{id}/simulate/authorization_reversal`: which authorization to reverse, and optionally how much of it.'
      properties:
        cardTransactionId:
          type: string
          description: The authorization to reverse. It must still be fully open, with nothing settled against it yet.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
        amount:
          type: integer
          format: int64
          description: How much to reverse, in the smallest unit of the transaction's currency (cents for USD). Leave it out to reverse the whole authorization. Send an amount to reverse only that much and leave the rest on hold.
          exclusiveMinimum: 0
          example: 1250
    SandboxCardDisputeOpenRequest:
      type: object
      required:
        - cardTransactionId
      description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/dispute_open`. Opens a dispute against a settled `CardTransaction`, as the card issuer does when a cardholder files a claim.
      properties:
        cardTransactionId:
          type: string
          description: The id of the settled `CardTransaction` to dispute. Must have at least one settled clearing and no dispute already open.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
        amount:
          type: integer
          format: int64
          description: The disputed amount in the smallest unit of the transaction's currency. Defaults to the settled amount; must not exceed it.
          exclusiveMinimum: 0
          example: 1500
        reason:
          type: string
          description: Free-text reason recorded on the opening event.
          example: Merchandise not received
    SandboxCardDisputeResponse:
      type: object
      required:
        - disputeId
        - disputeToken
        - status
      description: The simulated dispute after the request was applied. The dispute simulators report the resulting dispute in this response; the cardholder money a dispute event moves settles asynchronously.
      properties:
        disputeId:
          type: string
          description: The id of the dispute record.
          example: 019542f5-b3e7-1d02-0000-000000000200
        disputeToken:
          type: string
          description: The card issuer's token for the dispute.
          example: dispute_transaction_sandbox_5f8d2c1a
        status:
          type: string
          enum:
            - OPEN
            - CLOSED
          description: Whether the dispute is still active.
          example: OPEN
        disposition:
          type:
            - string
            - 'null'
          enum:
            - WON
            - PARTIALLY_WON
            - LOST
            - WITHDRAWN
            - DENIED
            - null
          description: The outcome once the dispute closes. Null while it is open.
          example: WON
    SandboxCardDisputeCreditRequest:
      type: object
      required:
        - cardTransactionId
        - amount
      description: 'Sandbox-only request body for the provisional-credit simulators: `POST /sandbox/cards/{id}/simulate/dispute_provisional_credit` and `POST /sandbox/cards/{id}/simulate/dispute_provisional_credit_reversal`.'
      properties:
        cardTransactionId:
          type: string
          description: The id of the disputed `CardTransaction`; it must have an open dispute.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
        amount:
          type: integer
          format: int64
          description: The amount to credit, or to take back, in the smallest unit of the transaction's currency. Must not exceed the disputed amount.
          exclusiveMinimum: 0
          example: 1500
    SandboxCardDisputeCloseRequest:
      type: object
      required:
        - cardTransactionId
        - disposition
      description: Sandbox-only request body for `POST /sandbox/cards/{id}/simulate/dispute_close`. Closes the transaction's open dispute with a disposition, as the card issuer does when the network process ends.
      properties:
        cardTransactionId:
          type: string
          description: The id of the disputed `CardTransaction`; it must have an open dispute.
          example: Transaction:019542f5-b3e7-1d02-0000-000000000100
        disposition:
          type: string
          enum:
            - WON
            - PARTIALLY_WON
            - LOST
            - WITHDRAWN
            - DENIED
          description: The outcome the dispute closes with.
          example: WON
        recoveredAmount:
          type: integer
          format: int64
          description: For `PARTIALLY_WON` only, how much of the disputed amount the network gave back, in the smallest unit of the transaction's currency. Must be strictly between 0 and the disputed amount; defaults to half.
          exclusiveMinimum: 0
          example: 400
    StablecoinIssuanceStatus:
      type: string
      enum:
        - PENDING_REVIEW
        - PROVISIONING
        - PROVISIONED
        - REJECTED
        - FAILED
      description: Provider issuance/linking status for the registered stablecoin.
    StablecoinGridOperationsStatus:
      type: string
      enum:
        - NOT_ENABLED
        - PENDING_APPROVAL
        - ENABLING
        - ENABLED
        - DISABLED
      description: Whether the stablecoin has been enabled for normal Grid operations.
    StablecoinProvider:
      type: string
      enum:
        - BRALE
      description: Stablecoin provider backing the linked account, stablecoin, or operation.
    StablecoinNetwork:
      type: string
      enum:
        - SPARK
      description: Public network family for the stablecoin token identifier. The concrete Spark network is inferred from the platform mode.
    StablecoinProviderEnvironment:
      type: string
      enum:
        - SANDBOX
        - PRODUCTION
      description: Provider environment derived from the authenticated Grid platform mode.
    Stablecoin:
      type: object
      required:
        - id
        - name
        - symbol
        - baseCurrency
        - network
        - decimals
        - issuanceStatus
        - gridOperationsStatus
        - networkTokenIdentifier
        - provider
        - stablecoinProviderAccountId
        - providerEnvironment
        - providerTokenIdentifier
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: System-generated stablecoin identifier.
          example: Stablecoin:019542f5-b3e7-1d02-0000-000000000002
        name:
          type: string
          description: Stablecoin display name.
          example: Acme Dollar
        symbol:
          type: string
          description: Stablecoin symbol.
          example: ACME
        baseCurrency:
          type: string
          description: Fiat currency the stablecoin is denominated against.
          example: USD
        network:
          $ref: '#/components/schemas/StablecoinNetwork'
        decimals:
          type: integer
          description: Number of decimal places used by the stablecoin.
          example: 6
        issuanceStatus:
          $ref: '#/components/schemas/StablecoinIssuanceStatus'
        gridOperationsStatus:
          $ref: '#/components/schemas/StablecoinGridOperationsStatus'
        networkTokenIdentifier:
          type: string
          description: Token identifier in the namespace selected by `network`.
          example: btkn1qw508d6qejxtdg4y5r3zarvary0c5xw7k
        currency:
          type: string
          description: Grid currency code after the stablecoin is enabled for Grid operations.
          example: ACME
        provider:
          $ref: '#/components/schemas/StablecoinProvider'
        stablecoinProviderAccountId:
          type: string
          description: Stablecoin provider account link selected for this stablecoin.
          example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001
        providerEnvironment:
          $ref: '#/components/schemas/StablecoinProviderEnvironment'
        providerTokenIdentifier:
          type: string
          description: Provider token identifier. For Brale this maps to `value_type`.
          example: ACME
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp.
          example: '2026-05-20T16:40:00Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp.
          example: '2026-05-20T17:10:00Z'
    StablecoinListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Stablecoin'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page.
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results.
        totalCount:
          type: integer
          description: Total number of stablecoins matching the criteria.
    StablecoinRegisterRequest:
      type: object
      required:
        - providerTokenIdentifier
        - networkTokenIdentifier
        - network
      properties:
        providerTokenIdentifier:
          type: string
          description: Provider identifier for the stablecoin. For Brale this maps to `value_type`.
          example: ACME
        networkTokenIdentifier:
          type: string
          maxLength: 255
          description: Token identifier in the namespace selected by `network`.
          example: btkn1qw508d6qejxtdg4y5r3zarvary0c5xw7k
        network:
          $ref: '#/components/schemas/StablecoinNetwork'
        stablecoinProviderAccountId:
          type: string
          description: Provider account link to register against. Optional when exactly one active link exists for the authenticated platform; required to resolve STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED when multiple links exist.
          example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001
    StablecoinMintFundingSourceBase:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - WIRE
            - ACH_DEBIT
            - SAME_DAY_ACH_DEBIT
            - GRID_INTERNAL_ACCOUNT
            - PROVIDER_INTERNAL_BALANCE
          description: Funding source variant. Grid internal account and provider internal balance funding are reserved for follow-up support.
          example: ACH_DEBIT
    StablecoinWireFundingSource:
      title: Wire
      allOf:
        - $ref: '#/components/schemas/StablecoinMintFundingSourceBase'
        - type: object
          required:
            - type
          properties:
            type:
              type: string
              enum:
                - WIRE
              description: Wire-funded mint. Funding instructions are returned on the resulting operation.
              example: WIRE
          description: Wire funding source. The issuer wires fiat to the provider using the returned funding instructions.
    StablecoinAchDebitFundingSource:
      title: ACH Debit
      allOf:
        - $ref: '#/components/schemas/StablecoinMintFundingSourceBase'
        - type: object
          required:
            - type
            - externalAccountId
          properties:
            type:
              type: string
              enum:
                - ACH_DEBIT
              description: ACH debit funding.
              example: ACH_DEBIT
            externalAccountId:
              type: string
              description: Grid `ExternalAccount` debited to fund the mint.
              example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101
          description: ACH debit funding source. The provider debits the referenced external account.
    StablecoinSameDayAchDebitFundingSource:
      title: Same-Day ACH Debit
      allOf:
        - $ref: '#/components/schemas/StablecoinMintFundingSourceBase'
        - type: object
          required:
            - type
            - externalAccountId
          properties:
            type:
              type: string
              enum:
                - SAME_DAY_ACH_DEBIT
              description: Same-day ACH debit funding.
              example: SAME_DAY_ACH_DEBIT
            externalAccountId:
              type: string
              description: Grid `ExternalAccount` debited to fund the mint.
              example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101
          description: Same-day ACH debit funding source. The provider debits the referenced external account.
    StablecoinGridInternalFundingSource:
      title: Grid Internal Account
      allOf:
        - $ref: '#/components/schemas/StablecoinMintFundingSourceBase'
        - type: object
          required:
            - type
            - accountId
          properties:
            type:
              type: string
              enum:
                - GRID_INTERNAL_ACCOUNT
              description: Grid internal account funding. Reserved for the follow-up Grid-funded mint flow.
              example: GRID_INTERNAL_ACCOUNT
            accountId:
              type: string
              description: Grid internal funding account id.
              example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102
          description: Grid internal account funding source, reserved for future Grid-funded mint flows.
    StablecoinProviderBalanceFundingSource:
      title: Provider Internal Balance
      allOf:
        - $ref: '#/components/schemas/StablecoinMintFundingSourceBase'
        - type: object
          required:
            - type
            - sourceTokenIdentifier
            - cryptoNetwork
          properties:
            type:
              type: string
              enum:
                - PROVIDER_INTERNAL_BALANCE
              description: Provider internal balance funding. Reserved for the follow-up provider-balance mint flow.
              example: PROVIDER_INTERNAL_BALANCE
            sourceTokenIdentifier:
              type: string
              description: Provider token/value type spent from the provider internal balance.
              example: USDC
            cryptoNetwork:
              type: string
              description: Source crypto network for the provider internal balance funds.
              example: SOLANA
          description: Provider internal balance funding source, reserved for future provider-funded mint flows.
    StablecoinMintFundingSource:
      oneOf:
        - $ref: '#/components/schemas/StablecoinWireFundingSource'
        - $ref: '#/components/schemas/StablecoinAchDebitFundingSource'
        - $ref: '#/components/schemas/StablecoinSameDayAchDebitFundingSource'
        - $ref: '#/components/schemas/StablecoinGridInternalFundingSource'
        - $ref: '#/components/schemas/StablecoinProviderBalanceFundingSource'
      discriminator:
        propertyName: type
        mapping:
          WIRE: '#/components/schemas/StablecoinWireFundingSource'
          ACH_DEBIT: '#/components/schemas/StablecoinAchDebitFundingSource'
          SAME_DAY_ACH_DEBIT: '#/components/schemas/StablecoinSameDayAchDebitFundingSource'
          GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalFundingSource'
          PROVIDER_INTERNAL_BALANCE: '#/components/schemas/StablecoinProviderBalanceFundingSource'
    StablecoinMintDestination:
      type: object
      description: Account that receives the minted stablecoin. External and Grid-managed accounts are represented as a single account reference, matching the `accountId` pattern used elsewhere in the API; the id prefix (`ExternalAccount:` or `InternalAccount:`) determines which. In the initial Brale-backed flow this must reference an active Spark external account.
      required:
        - accountId
      properties:
        accountId:
          type: string
          description: Grid account receiving the minted stablecoin. Accepts an `ExternalAccount:` id (initial Brale-backed flow) or an `InternalAccount:` id for Grid-managed destinations.
          example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000201
    StablecoinMintRequest:
      type: object
      required:
        - amount
        - fundingSource
        - destination
      properties:
        amount:
          type: string
          pattern: ^[0-9]+$
          description: Amount to mint in the stablecoin's smallest unit. The amount must convert exactly to a whole fiat cent for the token decimals.
          example: '1000000'
        fundingSource:
          $ref: '#/components/schemas/StablecoinMintFundingSource'
        destination:
          $ref: '#/components/schemas/StablecoinMintDestination'
        description:
          type: string
          maxLength: 1024
          description: Optional platform-provided operation description.
          example: Initial program funding mint
    StablecoinOperationType:
      type: string
      enum:
        - MINT
        - BURN
      description: Stablecoin issuer operation type.
    StablecoinOperationStatus:
      type: string
      enum:
        - CREATED
        - PENDING_FUNDING
        - PENDING_PROVIDER
        - PROCESSING
        - COMPLETED
        - FAILED
        - EXPIRED
      description: Stablecoin issuer operation lifecycle status.
    StablecoinOperationSource:
      type: object
      description: 'Source of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged shape (superset of the mint funding source and burn source request variants) so the field is unambiguously deserializable regardless of operation type: `externalAccountId` is present for external-account funded mints, `accountId` for Grid internal account sources, and `sourceTokenIdentifier`/`cryptoNetwork` for provider internal balance sources.'
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - WIRE
            - ACH_DEBIT
            - SAME_DAY_ACH_DEBIT
            - EXTERNAL_ACCOUNT
            - GRID_INTERNAL_ACCOUNT
            - PROVIDER_INTERNAL_BALANCE
          description: Source variant. Mint funding uses `WIRE`, `ACH_DEBIT`, or `SAME_DAY_ACH_DEBIT`; burns use `EXTERNAL_ACCOUNT`. `GRID_INTERNAL_ACCOUNT` and `PROVIDER_INTERNAL_BALANCE` are shared across mint and burn.
          example: ACH_DEBIT
        externalAccountId:
          type: string
          description: Grid `ExternalAccount` funding the operation, present for ACH debit mints.
          example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000101
        accountId:
          type: string
          description: Grid internal account id, present for Grid internal account sources.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000102
        sourceTokenIdentifier:
          type: string
          description: Provider token/value type, present for provider internal balance sources.
          example: USDC
        cryptoNetwork:
          type: string
          description: Source crypto network, present for provider internal balance sources.
          example: SOLANA
    StablecoinFundingInstructions:
      type: object
      description: Provider funding instructions safe to show publicly. Present for wire-funded mint operations and external-source burn operations when available. The common fields below are always declared; the exact set of remaining fields is rail- and provider-specific (for example, wire instructions carry bank beneficiary/account/routing details while Spark and on-chain instructions carry a deposit address), so `additionalProperties` stays open to pass those through without a spec change per rail.
      additionalProperties: true
      properties:
        rail:
          type: string
          description: Funding rail these instructions apply to (e.g. `SPARK`, `WIRE`).
          example: SPARK
        network:
          type: string
          description: Network the funds should be sent on, when applicable.
          example: SPARK
        address:
          type: string
          description: Deposit address the issuer sends funds to, for on-chain or Spark rails.
          example: spark1...
        valueType:
          type: string
          description: Provider token/value type the deposit address expects.
          example: ACME
      example:
        rail: SPARK
        network: SPARK
        address: spark1...
        valueType: ACME
    StablecoinOperationDestination:
      type: object
      description: 'Destination of a stablecoin operation, as reported on a `StablecoinOperation`. A single merged (flat) shape so the field is unambiguously deserializable regardless of operation type: `accountId` identifies the receiving account (its `ExternalAccount:` / `InternalAccount:` prefix disambiguates external vs Grid-managed), and `rail` is present for burn (fiat payout) destinations and absent for mint destinations.'
      required:
        - accountId
      properties:
        accountId:
          type: string
          description: Grid account that received the funds. An `ExternalAccount:` id for external destinations or an `InternalAccount:` id for Grid-managed destinations.
          example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205
        rail:
          type: string
          enum:
            - WIRE
            - ACH_CREDIT
            - SAME_DAY_ACH_CREDIT
            - RTP_CREDIT
          description: Fiat payout rail, present for burn (fiat redemption) destinations.
    StablecoinEstimatedDelivery:
      type: object
      description: Static rail timing estimate for fiat redemption delivery. This is guidance, not a guaranteed arrival time.
      additionalProperties: true
      properties:
        rail:
          type: string
          description: Fiat payout rail the estimate applies to.
          example: ACH_CREDIT
        amount:
          type: string
          pattern: ^[0-9]+$
          description: Estimated delivery amount in the smallest unit of `currency` (e.g. cents for USD).
          example: '100'
        currency:
          type: string
          description: ISO 4217 currency code of the fiat payout.
          example: USD
        timing:
          type: string
          description: Human-readable rail timing estimate.
          example: 1-3 business days
      example:
        rail: ACH_CREDIT
        amount: '100'
        currency: USD
        timing: 1-3 business days
    StablecoinOperation:
      type: object
      required:
        - id
        - stablecoinId
        - operationType
        - status
        - amount
        - provider
        - providerEnvironment
        - stablecoinProviderAccountId
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: System-generated stablecoin issuer operation identifier.
          example: StablecoinOperation:019542f5-b3e7-1d02-0000-000000000301
        stablecoinId:
          type: string
          description: Stablecoin this operation belongs to.
          example: Stablecoin:019542f5-b3e7-1d02-0000-000000000002
        operationType:
          $ref: '#/components/schemas/StablecoinOperationType'
        status:
          $ref: '#/components/schemas/StablecoinOperationStatus'
        amount:
          type: string
          description: Operation amount in the stablecoin's smallest unit.
          example: '1000000'
        provider:
          $ref: '#/components/schemas/StablecoinProvider'
        providerEnvironment:
          $ref: '#/components/schemas/StablecoinProviderEnvironment'
        stablecoinProviderAccountId:
          type: string
          description: Stablecoin provider account link used for the operation.
          example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001
        source:
          $ref: '#/components/schemas/StablecoinOperationSource'
        fundingInstructions:
          $ref: '#/components/schemas/StablecoinFundingInstructions'
        destination:
          $ref: '#/components/schemas/StablecoinOperationDestination'
        estimatedDelivery:
          $ref: '#/components/schemas/StablecoinEstimatedDelivery'
        expiresAt:
          type: string
          format: date-time
          description: Expiry for operations awaiting external funding.
          example: '2026-05-27T16:40:00Z'
        providerStatus:
          type: string
          description: Sanitized provider status when available.
          example: pending
        failureReason:
          type: string
          description: Stable internal failure code for terminal failed operations.
          example: PROVIDER_TRANSFER_FAILED
        description:
          type: string
          description: Platform-provided operation description.
          example: Initial program funding mint
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp.
          example: '2026-05-20T16:40:00Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp.
          example: '2026-05-20T17:10:00Z'
    Error503:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: |
            | Error Code | Description |
            |------------|-------------|
            | SERVICE_UNAVAILABLE | Downstream service is temporarily unavailable |
          enum:
            - SERVICE_UNAVAILABLE
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    StablecoinBurnSourceBase:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - EXTERNAL_ACCOUNT
            - GRID_INTERNAL_ACCOUNT
            - PROVIDER_INTERNAL_BALANCE
          description: Burn source variant. Grid internal account burns are reserved for the Grid-enabled ledger movement flow.
          example: EXTERNAL_ACCOUNT
    StablecoinExternalAccountBurnSource:
      title: External Account
      allOf:
        - $ref: '#/components/schemas/StablecoinBurnSourceBase'
        - type: object
          required:
            - type
          properties:
            type:
              type: string
              enum:
                - EXTERNAL_ACCOUNT
              description: External account burn. The response carries funding instructions naming the provider deposit address to send the stablecoin to.
              example: EXTERNAL_ACCOUNT
          description: External account burn source. The issuer sends the stablecoin to the provider deposit address returned in the operation's funding instructions; it may be sent from any wallet.
    StablecoinGridInternalBurnSource:
      title: Grid Internal Account
      allOf:
        - $ref: '#/components/schemas/StablecoinBurnSourceBase'
        - type: object
          required:
            - type
            - accountId
          properties:
            type:
              type: string
              enum:
                - GRID_INTERNAL_ACCOUNT
              description: Grid internal account burn. Reserved for the Grid-controlled ledger movement flow.
              example: GRID_INTERNAL_ACCOUNT
            accountId:
              type: string
              description: Grid internal source account id.
              example: InternalAccount:019542f5-b3e7-1d02-0000-000000000204
          description: Grid internal account burn source, reserved for future Grid-controlled burn flows.
    StablecoinProviderBalanceBurnSource:
      title: Provider Internal Balance
      allOf:
        - $ref: '#/components/schemas/StablecoinBurnSourceBase'
        - type: object
          required:
            - type
          properties:
            type:
              type: string
              enum:
                - PROVIDER_INTERNAL_BALANCE
              description: Provider internal balance burn. Spends stablecoin funds already held at the provider.
              example: PROVIDER_INTERNAL_BALANCE
          description: Provider internal balance burn source. No external funding is required; the provider spends existing balance.
    StablecoinBurnSource:
      oneOf:
        - $ref: '#/components/schemas/StablecoinExternalAccountBurnSource'
        - $ref: '#/components/schemas/StablecoinGridInternalBurnSource'
        - $ref: '#/components/schemas/StablecoinProviderBalanceBurnSource'
      discriminator:
        propertyName: type
        mapping:
          EXTERNAL_ACCOUNT: '#/components/schemas/StablecoinExternalAccountBurnSource'
          GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalBurnSource'
          PROVIDER_INTERNAL_BALANCE: '#/components/schemas/StablecoinProviderBalanceBurnSource'
    StablecoinBurnDestinationRail:
      type: string
      enum:
        - WIRE
        - ACH_CREDIT
        - SAME_DAY_ACH_CREDIT
        - RTP_CREDIT
      description: Fiat payout rail for the burn destination.
    StablecoinBurnDestinationBase:
      type: object
      required:
        - type
        - rail
      properties:
        type:
          type: string
          enum:
            - EXTERNAL_ACCOUNT
            - GRID_INTERNAL_ACCOUNT
          description: Burn destination variant.
        rail:
          $ref: '#/components/schemas/StablecoinBurnDestinationRail'
    StablecoinExternalAccountBurnDestination:
      title: External Account
      allOf:
        - $ref: '#/components/schemas/StablecoinBurnDestinationBase'
        - type: object
          required:
            - type
            - externalAccountId
          properties:
            type:
              type: string
              enum:
                - EXTERNAL_ACCOUNT
              description: External account burn destination.
              example: EXTERNAL_ACCOUNT
            externalAccountId:
              type: string
              description: Grid bank `ExternalAccount` receiving fiat redemption proceeds.
              example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000205
    StablecoinGridInternalBurnDestination:
      title: Grid Internal Account
      allOf:
        - $ref: '#/components/schemas/StablecoinBurnDestinationBase'
        - type: object
          required:
            - type
            - accountId
          properties:
            type:
              type: string
              enum:
                - GRID_INTERNAL_ACCOUNT
              description: Grid internal account burn destination.
              example: GRID_INTERNAL_ACCOUNT
            accountId:
              type: string
              description: Grid `InternalAccount` receiving fiat redemption proceeds through its bank funding instructions.
              example: InternalAccount:019542f5-b3e7-1d02-0000-000000000206
    StablecoinBurnDestination:
      oneOf:
        - $ref: '#/components/schemas/StablecoinExternalAccountBurnDestination'
        - $ref: '#/components/schemas/StablecoinGridInternalBurnDestination'
      discriminator:
        propertyName: type
        mapping:
          EXTERNAL_ACCOUNT: '#/components/schemas/StablecoinExternalAccountBurnDestination'
          GRID_INTERNAL_ACCOUNT: '#/components/schemas/StablecoinGridInternalBurnDestination'
    StablecoinBurnRequest:
      type: object
      required:
        - amount
        - source
        - destination
      properties:
        amount:
          type: string
          pattern: ^[0-9]+$
          description: Amount to burn in the stablecoin's smallest unit. The amount must convert exactly to a whole fiat cent for the token decimals.
          example: '1000000'
        source:
          $ref: '#/components/schemas/StablecoinBurnSource'
        destination:
          $ref: '#/components/schemas/StablecoinBurnDestination'
        description:
          type: string
          maxLength: 1024
          description: Optional platform-provided operation description.
          example: Redeem ACME
    StablecoinOperationListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/StablecoinOperation'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page.
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results.
        totalCount:
          type: integer
          description: Total number of stablecoin operations matching the query.
    StablecoinProviderAccountStatus:
      type: string
      enum:
        - ACTIVE
        - INVALID
        - REVOKED
      description: Status of the linked stablecoin provider account credentials.
    StablecoinProviderAccount:
      type: object
      required:
        - id
        - provider
        - providerAccountId
        - providerEnvironment
        - status
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          description: System-generated stablecoin provider account link identifier.
          example: StablecoinProviderAccount:019542f5-b3e7-1d02-0000-000000000001
        provider:
          $ref: '#/components/schemas/StablecoinProvider'
        providerAccountId:
          type: string
          description: Provider account id.
          example: 2VcUIonJeVQzFoBuC7LdFT0dRe4
        providerEnvironment:
          $ref: '#/components/schemas/StablecoinProviderEnvironment'
        status:
          $ref: '#/components/schemas/StablecoinProviderAccountStatus'
        lastVerifiedAt:
          type: string
          format: date-time
          description: Timestamp of the last successful provider credential verification.
          example: '2026-05-20T16:35:00Z'
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp.
          example: '2026-05-20T16:35:00Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp.
          example: '2026-05-20T16:35:00Z'
    StablecoinProviderAccountListResponse:
      type: object
      required:
        - data
        - hasMore
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/StablecoinProviderAccount'
        hasMore:
          type: boolean
          description: Indicates if more results are available beyond this page.
        nextCursor:
          type: string
          description: Cursor to retrieve the next page of results.
        totalCount:
          type: integer
          description: Total number of stablecoin provider account links matching the criteria.
    StablecoinProviderAccountLinkRequest:
      type: object
      required:
        - provider
        - apiClientId
        - apiClientSecret
      properties:
        provider:
          $ref: '#/components/schemas/StablecoinProvider'
        apiClientId:
          type: string
          description: Provider API client id used to verify and link the provider account.
          example: provider_client_123
        apiClientSecret:
          type: string
          writeOnly: true
          description: Provider API client secret. Grid verifies and stores this secret encrypted; it is never returned.
          example: provider_secret_456
        providerAccountId:
          type: string
          description: Provider account id. Optional when the submitted credentials expose exactly one provider account.
          example: 2VcUIonJeVQzFoBuC7LdFT0dRe4
    WebhookType:
      type: string
      enum:
        - OUTGOING_PAYMENT.PENDING
        - OUTGOING_PAYMENT.PENDING_AUTHORIZATION
        - OUTGOING_PAYMENT.PROCESSING
        - OUTGOING_PAYMENT.COMPLETED
        - OUTGOING_PAYMENT.FAILED
        - OUTGOING_PAYMENT.EXPIRED
        - OUTGOING_PAYMENT.REFUND_PENDING
        - OUTGOING_PAYMENT.REFUND_COMPLETED
        - OUTGOING_PAYMENT.REFUND_FAILED
        - INCOMING_PAYMENT.PENDING
        - INCOMING_PAYMENT.PROCESSING
        - INCOMING_PAYMENT.COMPLETED
        - INCOMING_PAYMENT.FAILED
        - INCOMING_PAYMENT.REFUND_PENDING
        - INCOMING_PAYMENT.REFUND_COMPLETED
        - INCOMING_PAYMENT.REFUND_FAILED
        - CUSTOMER.KYC_APPROVED
        - CUSTOMER.KYC_REJECTED
        - CUSTOMER.KYC_PENDING
        - CUSTOMER.KYB_APPROVED
        - CUSTOMER.KYB_REJECTED
        - CUSTOMER.KYB_PENDING
        - VERIFICATION.APPROVED
        - VERIFICATION.REJECTED
        - VERIFICATION.RESOLVE_ERRORS
        - VERIFICATION.IN_PROGRESS
        - VERIFICATION.PENDING_MANUAL_REVIEW
        - VERIFICATION.READY_FOR_VERIFICATION
        - EXTERNAL_ACCOUNT.STATUS_UPDATED
        - INTERNAL_ACCOUNT.BALANCE_UPDATED
        - INTERNAL_ACCOUNT.STATUS_UPDATED
        - INTERNAL_ACCOUNT.FUNDING_INSTRUCTIONS_UPDATED
        - INVITATION.CLAIMED
        - BULK_UPLOAD.COMPLETED
        - BULK_UPLOAD.FAILED
        - AGENT_ACTION.PENDING_APPROVAL
        - CARD.STATUS_CHANGE
        - CARD.PIN_STATUS_CHANGE
        - CARD_TRANSACTION.AUTHORIZED
        - CARD_TRANSACTION.PARTIALLY_SETTLED
        - CARD_TRANSACTION.SETTLED
        - CARD_TRANSACTION.DECLINED
        - CARD_TRANSACTION.VOIDED
        - CARD_TRANSACTION.EXCEPTION
        - WALLET_OPERATION.COMPLETED
        - WALLET_OPERATION.FAILED
        - TEST
      description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status.
    BaseWebhook:
      type: object
      required:
        - timestamp
        - id
        - type
      properties:
        id:
          type: string
          description: Unique identifier for this webhook delivery (can be used for idempotency)
          example: Webhook:019542f5-b3e7-1d02-0000-000000000007
        type:
          $ref: '#/components/schemas/WebhookType'
          description: Status-specific event type in OBJECT.EVENT dot-notation (e.g., OUTGOING_PAYMENT.COMPLETED)
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the webhook was sent
          example: '2025-08-15T14:32:00Z'
    AgentActionWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/AgentAction'
            type:
              type: string
              enum:
                - AGENT_ACTION.PENDING_APPROVAL
    IncomingPaymentWebhookData:
      title: Incoming Payment Webhook Data
      allOf:
        - $ref: '#/components/schemas/IncomingTransaction'
        - type: object
          properties:
            requestedReceiverCustomerInfoFields:
              type: array
              items:
                $ref: '#/components/schemas/CounterpartyFieldDefinition'
              description: Information required by the sender's VASP about the recipient. Platform must provide these in the 200 OK response if approving. Note that this only includes fields which Grid does not already have from initial customer registration.
    IncomingPaymentWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/IncomingPaymentWebhookData'
            type:
              type: string
              enum:
                - INCOMING_PAYMENT.PENDING
                - INCOMING_PAYMENT.PROCESSING
                - INCOMING_PAYMENT.COMPLETED
                - INCOMING_PAYMENT.FAILED
                - INCOMING_PAYMENT.REFUND_PENDING
                - INCOMING_PAYMENT.REFUND_COMPLETED
                - INCOMING_PAYMENT.REFUND_FAILED
    IncomingPaymentWebhookResponse:
      type: object
      properties:
        receiverCustomerInfo:
          type: object
          additionalProperties: true
          description: Information about the recipient, provided by the platform if requested in the webhook via `requestedReceiverCustomerInfoFields` and the payment is approved.
    IncomingPaymentWebhookForbiddenResponse:
      allOf:
        - $ref: '#/components/schemas/GridError'
        - type: object
          properties:
            reason:
              type: string
              description: Optional reason for rejecting the payment. This is just for debugging purposes or can be used for a platform's own purposes.
              example: RESTRICTED_JURISDICTION
    IncomingPaymentWebhookUnprocessableResponse:
      allOf:
        - $ref: '#/components/schemas/GridError'
        - type: object
          properties:
            requiredFields:
              type: array
              items:
                type: string
              description: List of fields that are required by the platform, but are not present in the counterparty information.
              example:
                - TAX_ID
                - REGISTRATION_NUMBER
    OutgoingPaymentWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/OutgoingTransaction'
            type:
              type: string
              enum:
                - OUTGOING_PAYMENT.PENDING
                - OUTGOING_PAYMENT.PENDING_AUTHORIZATION
                - OUTGOING_PAYMENT.PROCESSING
                - OUTGOING_PAYMENT.COMPLETED
                - OUTGOING_PAYMENT.FAILED
                - OUTGOING_PAYMENT.EXPIRED
                - OUTGOING_PAYMENT.REFUND_PENDING
                - OUTGOING_PAYMENT.REFUND_COMPLETED
                - OUTGOING_PAYMENT.REFUND_FAILED
    TestWebhookRequest:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          properties:
            type:
              type: string
              enum:
                - TEST
    BulkUploadWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/BulkCustomerImportJob'
            type:
              type: string
              enum:
                - BULK_UPLOAD.COMPLETED
                - BULK_UPLOAD.FAILED
    InvitationClaimedWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/UmaInvitation'
            type:
              type: string
              enum:
                - INVITATION.CLAIMED
    CustomerWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/CustomerOneOf'
            type:
              type: string
              enum:
                - CUSTOMER.KYC_APPROVED
                - CUSTOMER.KYC_REJECTED
                - CUSTOMER.KYC_PENDING
                - CUSTOMER.KYB_APPROVED
                - CUSTOMER.KYB_REJECTED
                - CUSTOMER.KYB_PENDING
    InternalAccountStatusWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/InternalAccount'
            type:
              type: string
              enum:
                - INTERNAL_ACCOUNT.BALANCE_UPDATED
                - INTERNAL_ACCOUNT.STATUS_UPDATED
                - INTERNAL_ACCOUNT.FUNDING_INSTRUCTIONS_UPDATED
    ExternalAccountStatusWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/ExternalAccount'
            type:
              type: string
              enum:
                - EXTERNAL_ACCOUNT.STATUS_UPDATED
    VerificationWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/Verification'
            type:
              type: string
              enum:
                - VERIFICATION.APPROVED
                - VERIFICATION.REJECTED
                - VERIFICATION.RESOLVE_ERRORS
                - VERIFICATION.IN_PROGRESS
                - VERIFICATION.PENDING_MANUAL_REVIEW
    CardStatusChangeWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/Card'
            type:
              type: string
              enum:
                - CARD.STATUS_CHANGE
    CardPinStatusChangeWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/Card'
              description: The updated card, including its new pinStatus.
            type:
              type: string
              enum:
                - CARD.PIN_STATUS_CHANGE
    CardTransactionWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/CardTransaction'
            type:
              type: string
              enum:
                - CARD_TRANSACTION.AUTHORIZED
                - CARD_TRANSACTION.PARTIALLY_SETTLED
                - CARD_TRANSACTION.SETTLED
                - CARD_TRANSACTION.DECLINED
                - CARD_TRANSACTION.VOIDED
                - CARD_TRANSACTION.EXCEPTION
    WalletOperationCompletedData:
      title: Wallet Operation Completed Data
      type: object
      required:
        - operationId
        - requestId
        - operationType
        - resourceType
        - resourceId
        - status
      properties:
        operationId:
          type: string
          description: Grid-internal identifier for this operation. Useful when contacting support about a specific operation; not a correlation key — use `requestId` to match this webhook to the request you made.
          example: Operation:019542f5-b3e7-1d02-0000-000000000099
        requestId:
          type: string
          description: 'The `Request-Id` you supplied on the signed retry that produced this terminal result — the same value you would have echoed on every retry had you received a `200 { status: "PROCESSING" }` response while it was settling. This is the primary way to correlate this webhook to the request you made.'
          example: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45
        operationType:
          type: string
          description: The kind of operation that reached a terminal state.
          enum:
            - auth_credential.create
            - auth_credential.delete
            - session.revoke
            - wallet.export
            - customer.contact_update
          example: wallet.export
        resourceType:
          type: string
          description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.create` and `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`, `customer.contact_update` → `CUSTOMER`.'
          enum:
            - AUTH_METHOD
            - SESSION
            - INTERNAL_ACCOUNT
            - CUSTOMER
          example: INTERNAL_ACCOUNT
        resourceId:
          type: string
          description: LSID of the business resource this operation affected. For `auth_credential.create`, this is the **primary way to learn the new credential's id** — the request that created it can't have supplied one in advance. For `auth_credential.delete` and `session.revoke` it echoes the `AuthMethod:<uuid>` / `Session:<uuid>` you already knew and referenced in the request; for `wallet.export` it's the `InternalAccount:<uuid>` whose wallet was exported; for `customer.contact_update` it's the `Customer:<uuid>` whose email or phone number changed.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000005
        status:
          type: string
          enum:
            - completed
          description: Terminal status of the operation.
          example: completed
    OperationError:
      type: object
      description: Failure details for a terminally failed operation.
      required:
        - code
      properties:
        code:
          type: string
          description: |
            Machine-readable failure code for a `FAILED` operation. Codes are
            Grid-defined and stable regardless of which vendor Grid uses under the
            hood for a given operation.
          example: SIGNER_PROVIDER_REJECTED
    WalletOperationFailedData:
      title: Wallet Operation Failed Data
      type: object
      required:
        - operationId
        - requestId
        - operationType
        - resourceType
        - resourceId
        - status
        - error
      properties:
        operationId:
          type: string
          description: Grid-internal identifier for this operation. Useful when contacting support about a specific operation; not a correlation key — use `requestId` to match this webhook to the request you made.
          example: Operation:019542f5-b3e7-1d02-0000-000000000099
        requestId:
          type: string
          description: 'The `Request-Id` you supplied on the signed retry that produced this terminal result — the same value you would have echoed on every retry had you received a `200 { status: "PROCESSING" }` response while it was settling. This is the primary way to correlate this webhook to the request you made.'
          example: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45
        operationType:
          type: string
          description: The kind of operation that reached a terminal state.
          enum:
            - auth_credential.create
            - auth_credential.delete
            - session.revoke
            - wallet.export
            - customer.contact_update
          example: wallet.export
        resourceType:
          type: string
          description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.create` and `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`, `customer.contact_update` → `CUSTOMER`.'
          enum:
            - AUTH_METHOD
            - SESSION
            - INTERNAL_ACCOUNT
            - CUSTOMER
          example: SESSION
        resourceId:
          type: string
          description: LSID of the business resource this operation affected. For `auth_credential.create`, this is the **primary way to learn the new credential's id** — the request that created it can't have supplied one in advance. For `auth_credential.delete` and `session.revoke` it echoes the `AuthMethod:<uuid>` / `Session:<uuid>` you already knew and referenced in the request; for `wallet.export` it's the `InternalAccount:<uuid>` whose wallet was exported; for `customer.contact_update` it's the `Customer:<uuid>` whose email or phone number changed.
          example: Session:019542f5-b3e7-1d02-0000-00000000009a
        status:
          type: string
          enum:
            - failed
          description: Terminal status of the operation.
          example: failed
        error:
          $ref: '#/components/schemas/OperationError'
    WalletOperationWebhookData:
      title: Wallet Operation Data
      oneOf:
        - $ref: '#/components/schemas/WalletOperationCompletedData'
        - $ref: '#/components/schemas/WalletOperationFailedData'
      discriminator:
        propertyName: status
        mapping:
          completed: '#/components/schemas/WalletOperationCompletedData'
          failed: '#/components/schemas/WalletOperationFailedData'
    WalletOperationWebhook:
      allOf:
        - $ref: '#/components/schemas/BaseWebhook'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/WalletOperationWebhookData'
            type:
              type: string
              enum:
                - WALLET_OPERATION.COMPLETED
                - WALLET_OPERATION.FAILED
  requestBodies:
    DocumentUploadRequestBody:
      required: true
      content:
        multipart/form-data:
          schema:
            $ref: '#/components/schemas/DocumentUploadRequest'
    DocumentReplaceRequestBody:
      required: true
      content:
        multipart/form-data:
          schema:
            $ref: '#/components/schemas/DocumentReplaceRequest'
    PaymentDocumentUploadRequestBody:
      required: true
      content:
        multipart/form-data:
          schema:
            $ref: '#/components/schemas/PaymentDocumentUploadRequest'
