> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-09-11-grid-api-agreement-consents.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Issue a card

> 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.supportsSpendLimits` 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` state do not count toward the limit.

New cards start in `state: "PROCESSING"` while the card issuer provisions the card. The `card.state_change` webhook fires on each state transition, including the transition to `ACTIVE` (or to `CLOSED` with `stateReason: "ISSUER_REJECTED"` if provisioning fails).




## OpenAPI

````yaml https://app.stainless.com/api/spec/documented/grid/openapi.documented.yml post /cards
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: 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:
  /cards:
    post:
      tags:
        - Cards
      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.supportsSpendLimits` 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`
        state do not count toward the limit.


        New cards start in `state: "PROCESSING"` while the card issuer
        provisions the card. The `card.state_change` webhook fires on each state
        transition, including the transition to `ACTIVE` (or to `CLOSED` with
        `stateReason: "ISSUER_REJECTED"` if provisioning fails).
      operationId: createCard
      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`, with `INVALID_INPUT` when
            the `Idempotency-Key` header is missing or exceeds 255 characters or
            when a required `threeDSecurePassword` is missing, empty, or
            whitespace-only, with `FUNDING_SOURCE_INELIGIBLE` 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'
      security:
        - BasicAuth: []
      x-codeSamples:
        - lang: JavaScript
          source: |-
            import LightsparkGrid from '@lightsparkdev/grid';

            const client = new LightsparkGrid({
              username: process.env['GRID_CLIENT_ID'], // This is the default and can be omitted
              password: process.env['GRID_CLIENT_SECRET'], // This is the default and can be omitted
            });

            const card = await client.cards.issue({
              customerId: 'Customer:019542f5-b3e7-1d02-0000-000000000001',
              form: 'VIRTUAL',
              fundingSource: 'InternalAccount:019542f5-b3e7-1d02-0000-000000000002',
              'Idempotency-Key': '550e8400-e29b-41d4-a716-446655440000',
              maxSpendPerDay: 25000,
              maxSpendPerTransaction: 5000,
              maxTransactionsPerDay: 20,
              platformCardId: 'card-emp-001',
            });

            console.log(card.id);
        - lang: Python
          source: |-
            import os
            from grid import LightsparkGrid

            client = LightsparkGrid(
                username=os.environ.get("GRID_CLIENT_ID"),  # This is the default and can be omitted
                password=os.environ.get("GRID_CLIENT_SECRET"),  # This is the default and can be omitted
            )
            card = client.cards.issue(
                customer_id="Customer:019542f5-b3e7-1d02-0000-000000000001",
                form="VIRTUAL",
                funding_source="InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
                idempotency_key="550e8400-e29b-41d4-a716-446655440000",
                max_spend_per_day=25000,
                max_spend_per_transaction=5000,
                max_transactions_per_day=20,
                platform_card_id="card-emp-001",
            )
            print(card.id)
        - lang: Go
          source: "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/stainless-sdks/grid-go\"\n\t\"github.com/stainless-sdks/grid-go/option\"\n)\n\nfunc main() {\n\tclient := grid.NewClient(\n\t\toption.WithUsername(\"My Username\"),\n\t\toption.WithPassword(\"My Password\"),\n\t)\n\tcard, err := client.Cards.Issue(context.TODO(), grid.CardIssueParams{\n\t\tCardCreateRequest: grid.CardCreateRequestParam{\n\t\t\tCustomerID:    \"Customer:019542f5-b3e7-1d02-0000-000000000001\",\n\t\t\tForm:          grid.CardCreateRequestFormVirtual,\n\t\t\tFundingSource: \"InternalAccount:019542f5-b3e7-1d02-0000-000000000002\",\n\t\t},\n\t\tIdempotencyKey: \"550e8400-e29b-41d4-a716-446655440000\",\n\t})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf(\"%+v\\n\", card.ID)\n}\n"
        - lang: Kotlin
          source: |-
            package com.lightspark.grid.example

            import com.lightspark.grid.client.LightsparkGridClient
            import com.lightspark.grid.client.okhttp.LightsparkGridOkHttpClient
            import com.lightspark.grid.models.cards.Card
            import com.lightspark.grid.models.cards.CardCreateRequest
            import com.lightspark.grid.models.cards.CardIssueParams

            fun main() {
                val client: LightsparkGridClient = LightsparkGridOkHttpClient.fromEnv()

                val params: CardIssueParams = CardIssueParams.builder()
                    .idempotencyKey("550e8400-e29b-41d4-a716-446655440000")
                    .cardCreateRequest(CardCreateRequest.builder()
                        .customerId("Customer:019542f5-b3e7-1d02-0000-000000000001")
                        .form(CardCreateRequest.Form.VIRTUAL)
                        .fundingSource("InternalAccount:019542f5-b3e7-1d02-0000-000000000002")
                        .build())
                    .build()
                val card: Card = client.cards().issue(params)
            }
        - lang: Ruby
          source: >-
            require "grid"


            lightspark_grid = Grid::Client.new(username: "My Username",
            password: "My Password")


            card = lightspark_grid.cards.issue(
              customer_id: "Customer:019542f5-b3e7-1d02-0000-000000000001",
              form: :VIRTUAL,
              funding_source: "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
              idempotency_key: "550e8400-e29b-41d4-a716-446655440000"
            )


            puts(card)
        - lang: PHP
          source: |-
            <?php

            require_once dirname(__DIR__) . '/vendor/autoload.php';

            use Grid\Client;
            use Grid\Core\Exceptions\APIException;

            $client = new Client(
              username: getenv('GRID_CLIENT_ID') ?: 'My Username',
              password: getenv('GRID_CLIENT_SECRET') ?: 'My Password',
            );

            try {
              $card = $client->cards->issue(
                customerID: 'Customer:019542f5-b3e7-1d02-0000-000000000001',
                form: 'VIRTUAL',
                fundingSource: 'InternalAccount:019542f5-b3e7-1d02-0000-000000000002',
                idempotencyKey: '550e8400-e29b-41d4-a716-446655440000',
                maxSpendPerDay: 25000,
                maxSpendPerTransaction: 5000,
                maxTransactionsPerDay: 20,
                platformCardID: 'card-emp-001',
                threeDSecurePassword: 'AbCd1234EfGh5678',
              );

              var_dump($card);
            } catch (APIException $e) {
              echo $e->getMessage();
            }
        - lang: CLI
          source: |-
            grid cards issue \
              --username 'My Username' \
              --password 'My Password' \
              --customer-id Customer:019542f5-b3e7-1d02-0000-000000000001 \
              --form VIRTUAL \
              --funding-source InternalAccount:019542f5-b3e7-1d02-0000-000000000002 \
              --idempotency-key 550e8400-e29b-41d4-a716-446655440000
components:
  schemas:
    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 `FUNDING_SOURCE_INELIGIBLE`.
          example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
        maxSpendPerTransaction:
          type: integer
          format: int64
          minimum: 1
          maximum: 9007199254740991
          description: >-
            Optional card-specific cap on a single transaction, in the smallest
            unit of the card currency derived from its funding source. Omit this
            field for no card-specific cap. When the platform config also
            supplies `cardConfigs.maxSpendPerTransaction`, Grid enforces the
            lower of the two values. Accepted only when the funding-source
            internal account's `cardCapabilities.supportsSpendLimits` is true. A
            transaction for exactly the effective limit is allowed.
          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 currency derived from
            its funding source. 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.supportsSpendLimits` 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
    Card:
      type: object
      required:
        - id
        - customerId
        - state
        - 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
        state:
          $ref: '#/components/schemas/CardState'
        stateReason:
          $ref: '#/components/schemas/CardStateReason'
          description: >-
            Reason associated with the current `state`. Present when the card is
            `CLOSED` or when provisioning was rejected; absent otherwise.
        brand:
          $ref: '#/components/schemas/CardBrand'
        form:
          $ref: '#/components/schemas/CardForm'
        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: >-
            Card-specific cap on a single transaction, in the smallest unit of
            the card's `currency`. Null means the card has no card-specific cap.
            When the platform config also supplies
            `cardConfigs.maxSpendPerTransaction`, Grid enforces the lower of the
            two values without replacing this configured value. A transaction
            for exactly the effective limit is allowed.
          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 (ISO 4217 for fiat, tickers for
            crypto). Derived from the funding source at issue time.
          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'
    Error400:
      type: object
      required:
        - message
        - status
        - code
      properties:
        status:
          type: integer
          enum:
            - 400
          description: HTTP status code
        code:
          type: string
          description: >
            | Error Code | Description |

            |------------|-------------|

            | INVALID_INPUT | Invalid input provided |

            | END_USER_TERMS_VERSION_NOT_FOUND | The submitted End User Terms
            version is not supported |

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

            | QUOTE_REQUEST_FAILED | An issue occurred during the quote process;
            this is retryable |

            | INVALID_PAYREQ_RESPONSE | Counterparty Payreq response was invalid
            |

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

            | LOOKUP_REQUEST_FAILED | Lookup request failed |

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

            | 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 |
          enum:
            - INVALID_INPUT
            - END_USER_TERMS_VERSION_NOT_FOUND
            - MISSING_MANDATORY_USER_INFO
            - INVITATION_ALREADY_CLAIMED
            - INVITATIONS_NOT_CONFIGURED
            - INVALID_UMA_ADDRESS
            - INVITATION_CANCELLED
            - QUOTE_REQUEST_FAILED
            - INVALID_PAYREQ_RESPONSE
            - 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
            - LOOKUP_REQUEST_FAILED
            - 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
            - 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
        message:
          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'
          additionalProperties: true
    Error401:
      type: object
      required:
        - message
        - status
        - code
      properties:
        status:
          type: integer
          enum:
            - 401
          description: HTTP status code
        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
        message:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error409:
      type: object
      required:
        - message
        - status
        - code
      properties:
        status:
          type: integer
          enum:
            - 409
          description: HTTP status code
        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 |

            | INVALID_STATE_TRANSITION | The requested card `state` transition
            is not one of `ACTIVE ⇄ FROZEN` or `ACTIVE \| FROZEN → CLOSED` |

            | CARD_ALREADY_CLOSED | `state: CLOSED` was requested for a card
            that is already `CLOSED` |

            | CARD_NOT_MUTABLE | The card is `CLOSED`, so it can no longer be
            mutated |

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

            | 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 |
          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
            - INVALID_STATE_TRANSITION
            - CARD_ALREADY_CLOSED
            - CARD_NOT_MUTABLE
            - CARD_LIMIT_REACHED
            - STABLECOIN_GRID_ENABLEMENT_NOT_REQUESTABLE
            - CONFLICT
        message:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error500:
      type: object
      required:
        - message
        - status
        - code
      properties:
        status:
          type: integer
          enum:
            - 500
          description: HTTP status code
        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
        message:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error501:
      type: object
      required:
        - message
        - status
        - code
      properties:
        status:
          type: integer
          enum:
            - 501
          description: HTTP status code
        code:
          type: string
          description: >
            | Error Code | Description |

            |------------|-------------|

            | UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY | Unrecognized mandatory
            payee data key |

            | NOT_IMPLEMENTED | Feature not implemented |
          enum:
            - UNRECOGNIZED_MANDATORY_PAYEE_DATA_KEY
            - NOT_IMPLEMENTED
        message:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    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.
    CardState:
      type: string
      enum:
        - PENDING_KYC
        - PROCESSING
        - ACTIVE
        - FROZEN
        - CLOSED
      description: >
        Lifecycle state of a card.


        | State | Description |

        |-------|-------------|

        | `PENDING_KYC` | The cardholder has not yet completed KYC. Cards in
        this state cannot transact. |

        | `PROCESSING` | The card has been requested and is being provisioned
        with the issuer. |

        | `ACTIVE` | The card is live and can authorize transactions. |

        | `FROZEN` | The card is temporarily disabled by the platform. New
        authorizations are declined with `CARD_PAUSED`. Existing settlements and
        refunds continue to reconcile. |

        | `CLOSED` | The card is permanently closed. Terminal, irreversible
        state. |
    CardStateReason:
      type: string
      enum:
        - ISSUER_REJECTED
        - CLOSED_BY_PLATFORM
        - CLOSED_BY_GRID
      description: >
        Reason a card reached a terminal or non-active state. Present on

        `CLOSED` cards, and on cards that fail provisioning before reaching

        `ACTIVE`.


        | Reason | Description |

        |--------|-------------|

        | `ISSUER_REJECTED` | The card issuer rejected provisioning during
        `PROCESSING`. |

        | `CLOSED_BY_PLATFORM` | The card was closed via `PATCH /cards/{id}`
        (`state: 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.
    CardCapabilities:
      type: object
      description: Actions supported by the card program associated with this resource.
      required:
        - supportsSpendLimits
        - supportsTransactionCountLimit
        - supports3dSecurePassword
        - supportsPanReveal
      properties:
        supportsSpendLimits:
          type: boolean
          description: >-
            Whether cards in this program accept `maxSpendPerTransaction` and
            `maxSpendPerDay`.
          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
    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.
    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
  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.

````