> ## Documentation Index
> Fetch the complete documentation index at: https://agents-docs.lightspark.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create an authentication credential

> 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. The account holder may have one `EMAIL_OTP` and one `SMS_OTP` credential per internal account; multiple distinct `PASSKEY` credentials may be registered. Administrators' credentials, added with `POST /auth/admin-credentials`, are not counted.

Adding a credential requires a signature from an existing verified credential on the same account. An administrator's session cannot sign it: a credential added here carries no `adminReference`, so it would outlive the administrator's removal. Grid refuses an administrator's signature with `403 FORBIDDEN`; administrators add their own credentials with `POST /auth/admin-credentials`. 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`.




## OpenAPI

````yaml /openapi.yaml post /auth/credentials
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: Payout Runs
    description: >-
      Endpoints for paying many recipients from one internal account in one
      request, and for reconciling the result
  - name: Settlement Rules
    description: >-
      Endpoints for standing instructions that split a customer's matching
      incoming payments among recipients as soon as the payments complete
  - 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, 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 a request requires approval, it is
      held as an agent action in `PENDING_APPROVAL` until the platform approves
      it via `POST /agents/{agentId}/actions/{actionId}/approve` or rejects it.
  - name: Claimable Payouts
    description: >-
      Endpoints the platform's claim page calls for a claimable payout an agent
      sent to a person by phone number or email: issue the claim token for the
      recipient's link, look the payout up by it, list how Grid can pay her,
      lock a quote to her account and confirm it.
  - 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.
  - name: Embedded Care
    description: |
      A support chat between the cardholder and Lightspark's care agent, driven
      from the partner's own app. The platform calls with its ordinary Grid
      credential, but the person typing is the cardholder rather than the
      caller, so reaching that cardholder's account takes a second proof: an
      SMS code they send into the chat, after which the partner carries a
      session token in the `X-Ls-Care-Session` header.

      **The surface is two routes, and verification happens inside the
      conversation.**

      1. **Open the chat.** `POST /care/chats/{chatId}/messages` with an empty
         `message`, addressed to the cardholder as `Customer:<uuid>`. Returns
         the chat's id and the agent's opening line in `reply`, which is what
         a chat window renders before anybody has typed. Sends no SMS, needs
         no `Idempotency-Key` and consumes none, and is safe to call again: a
         widget that reloaded gets the same chat back, this time without a
         `reply`.
      2. **Send the cardholder's first message.** The same route with their
         text and an `Idempotency-Key` header. One turn runs synchronously and
         the agent's answer comes back in `reply`. On a chat that cannot yet
         reach the account this call also texts the cardholder a code, and the
         agent's `reply` asks for it.
      3. **The cardholder sends the code as an ordinary message.** When it is
         correct, the response to that turn carries `sessionToken` and
         `expiresAt`. Send that token as the `X-Ls-Care-Session` header on
         every message from then on. A code that does not match is answered in
         `reply`, and the cardholder can simply send it again.
      4. **Close the chat** with `DELETE /care/chats/{chatId}` when the
         cardholder is done. Every response also carries `chatEnded`, and the
         agent can end the chat itself: when `chatEnded` is true, the closing
         line is in `reply` and the chat takes no further messages, so stop
         sending to it. An opening addressed to that `CareChat:<id>` answers
         `409 CARE_CHAT_ENDED`; address the cardholder as `Customer:<uuid>`
         to open a new chat.

      **Render `redactedCustomerMessage` if present, then `reply` if
      present.** That one rule covers an opening, a resume and a turn.

      **A cardholder whose code never arrived asks the agent for another
      one.** The agent sends a new code to the number on file and says so in
      its `reply`. There is no separate route for it.

      **The verification fields are optional hints.** `verificationRequired`
      and `codeLength` let an app show a dedicated one-time-code input of the
      right length, marked as a one-time code so iOS and Android offer to fill
      it from the SMS. A partner can ignore them, since the agent asks for the
      code in its reply and says where it went, and the app still sends the
      code as an ordinary message.

      Step 1 is optional. A message addressed to a `Customer:` id opens that
      cardholder's chat if they have none, so a partner whose cardholder has
      just typed something spends one call rather than two.

      A cardholder who has not verified yet is never a `401`, because the
      first message from every new cardholder is unverified and a `401` there
      would be indistinguishable from a bad credential. On these endpoints a
      `401` means the platform's Grid credential is wrong, the session token
      presented is malformed, or a replay lacks the session token its original
      request required.

      **Verification expires, and a lost session is recoverable in the
      chat.** A chat reaches the cardholder's account only while its
      verification is newer than the session lifetime, fifteen minutes in the
      default deployment, which is also how long a session token lives. Past
      that window a message gets a fresh code, and the agent asks the
      cardholder to send it back inside the conversation, which hands the
      partner the next session token. A new message sent with no
      `X-Ls-Care-Session` header, or with a token past its expiry, on a chat
      whose verification is still current is treated the same way: the
      verification is cancelled before the turn runs, a new code is sent, and
      the agent asks for it. So a client that lost its token, or held one too
      long, simply keeps sending messages. Only a token that is malformed, or
      that was minted for another platform, chat or cardholder, is refused.

      Two things an integration has to get right:

      - **Render `redactedCustomerMessage`, never your own copy of what was
        typed.** Grid masks card numbers and other sensitive digit strings in
        it. A UI that renders its own copy of the input instead puts a card
        number the cardholder pasted back on their screen unmasked, and into
        your logs.
      - **The session token is returned once and is never re-readable.** No
        endpoint hands it back, it cannot be revoked, and it is short-lived by
        design. Hold it for the life of the cardholder's session. It arrives
        on the message response to the turn in which the cardholder sent their
        code, and on no other. Losing it costs the cardholder a new code, never
        access to the chat.

      Some things are absent by construction, so no integration can come to
      depend on them: nothing returns a transcript or an earlier turn's reply
      (retrying under the original `Idempotency-Key` is how an answer that was
      produced but not delivered is recovered; a replay requires the same
      session token its original request required and never carries a
      `sessionToken`, so a lost token is recovered by sending the cardholder's
      next message as a new message, which verifies them again), nothing sets
      a verification state, and nothing changes the number a code is sent to.

      Errors from these endpoints carry `code` and `reason` only (see the
      `CareError` schema), unlike the `Error4xx` schemas used elsewhere in this
      specification.
paths:
  /auth/credentials:
    post:
      tags:
        - Embedded Wallet Auth
      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. The account
        holder may have one `EMAIL_OTP` and one `SMS_OTP` credential per
        internal account; multiple distinct `PASSKEY` credentials may be
        registered. Administrators' credentials, added with `POST
        /auth/admin-credentials`, are not counted.


        Adding a credential requires a signature from an existing verified
        credential on the same account. An administrator's session cannot sign
        it: a credential added here carries no `adminReference`, so it would
        outlive the administrator's removal. Grid refuses an administrator's
        signature with `403 FORBIDDEN`; administrators add their own credentials
        with `POST /auth/admin-credentials`. 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
      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 the account holder
            already has one (administrators' credentials are not counted),
            `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'
        '403':
          description: >-
            Forbidden. Returned with `FORBIDDEN` when the signed retry is signed
            by an administrator's session. Administrators add their own
            credentials with `POST /auth/admin-credentials`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error403'
        '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'
      security:
        - BasicAuth: []
components:
  schemas:
    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'
    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.
    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}`).
    Error400:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: >
            | Error Code | Description |

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

            | INVALID_INPUT | Invalid input provided. When Grid knows the cause,
            `details.refusalReason` names it. When a card cannot be issued
            because the customer record lacks fields, it is
            `CARDHOLDER_INFO_MISSING` and `details.missingFields` lists them |

            | 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 | The receiving institution declined this
            sender. Final; a retry returns the same answer |

            | 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 | The account holder already
            has an EMAIL_OTP credential on the target internal account; the
            holder may have one. Administrators' credentials, added with `POST
            /auth/admin-credentials`, are not counted |

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

            | STORED_CARD_VERIFICATION_FAILED | The card's issuer did not
            confirm the card: its security code, billing address or name did not
            match. Nothing was stored |

            | CARDHOLDER_KYC_NOT_APPROVED | The cardholder's KYC status is not
            `APPROVED`, so a card cannot be issued |

            | ACCOUNTHOLDER_AGREEMENT_NOT_ACCEPTED | The customer create request
            omits an accountholder agreement its accounts require |

            | CARDHOLDER_AGREEMENT_NOT_ACCEPTED | The customer has not accepted
            the cardholder agreement the platform requires before a card is
            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 |

            | EXACT_AMOUNT_UNAVAILABLE | The quote cannot deliver exactly the
            requested receiving amount at the current rate; quote again shortly
            or lock the sending amount |
          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
            - STORED_CARD_VERIFICATION_FAILED
            - CARDHOLDER_KYC_NOT_APPROVED
            - ACCOUNTHOLDER_AGREEMENT_NOT_ACCEPTED
            - CARDHOLDER_AGREEMENT_NOT_ACCEPTED
            - 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
            - EXACT_AMOUNT_UNAVAILABLE
        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'
            refusalReason:
              $ref: '#/components/schemas/RefusalReason'
            missingFields:
              type: array
              description: >-
                Each customer field the request needs but the customer record
                lacks, named as in `CustomerInfoFieldName` (for example
                `PHONE_NUMBER`). Present with refusal reason
                `CARDHOLDER_INFO_MISSING`. Grid may name fields not yet listed
                there.
              items:
                type: string
                example: PHONE_NUMBER
          additionalProperties: true
    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
    Error403:
      type: object
      required:
        - reason
        - code
      properties:
        code:
          type: string
          description: >
            | Error Code | Description |

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

            | FORBIDDEN | Insufficient permissions. `details.refusalReason`
            names the cause when Grid knows it |

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

            | DESTINATION_NOT_ALLOWED | The agent may not pay this destination |

            | CORRIDOR_UNDER_MAINTENANCE | Payments to this corridor are paused
            for maintenance. |
          enum:
            - FORBIDDEN
            - USER_NOT_READY
            - COUNTERPARTY_NOT_ALLOWED
            - VELOCITY_LIMIT_EXCEEDED
            - END_USER_TERMS_NOT_ACCEPTED
            - CUSTOMER_NOT_VERIFIED
            - SANCTION_BLOCKED
            - DESTINATION_NOT_ALLOWED
            - CORRIDOR_UNDER_MAINTENANCE
        reason:
          type: string
          description: Error message
        details:
          type: object
          description: >-
            Additional error details. Shape varies by `code`. A `FORBIDDEN`
            refusal with a known cause carries `refusalReason`, plus the fields
            that reason names.
          properties:
            refusalReason:
              $ref: '#/components/schemas/RefusalReason'
            permission:
              type: string
              description: >-
                The `AgentPermission` the agent's policy lacks. Present with
                refusal reason `AGENT_PERMISSION_REQUIRED`. Grid may add
                permissions, so accept a value not yet listed there.
              example: ISSUE_CARDS
            policyDenialReason:
              $ref: '#/components/schemas/AgentPolicyDenialReason'
              description: >-
                The policy rule that denied the quote or purchase card. Present
                with refusal reason `AGENT_POLICY_DENIED` or
                `AGENT_PURCHASE_CARD_POLICY_DENIED`.
          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
    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 |

            | PASSKEY_ALREADY_ENROLLED | The customer already has the most
            passkeys allowed (two). Delete 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_NOT_TRUSTED | The external account must be a trusted
            beneficiary before it can be used here. Trust it first via `POST
            /customers/external-accounts/{externalAccountId}/trust` (and its
            `/confirm`), then retry |

            | 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`, or
            the card cannot be unfrozen by the platform because Grid froze it
            for review, it was frozen as `COMPROMISED`, or its customer is not
            `ACTIVE` |

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

            | AGENT_CARD_EXISTS | The agent already holds a card that is not
            `CLOSED`, so it cannot issue another. Close that card first |

            | PURCHASE_CARD_LIMIT_REACHED | The agent already holds the most
            open purchase cards allowed, counting purchase cards awaiting
            approval. Use or close one first |

            | 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. When the cardholder
            already holds the one card the platform allows,
            `details.refusalReason` is `CARDHOLDER_HOLDS_CARD` |

            | DOCUMENT_ALREADY_EXISTS | A document of this type already exists
            for the holder; replace it with PUT |

            | DUPLICATE_EXTERNAL_ACCOUNT | The `platformAccountId` is already
            used, or, without one, the wallet is already registered. 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_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 |

            | INVOICE_ALREADY_USED | Another quote for the Lightning invoice can
            still be executed or funded, or was executed. An invoice has one
            live quote at a time; ask the recipient for a new one, or retry once
            Grid has closed the earlier quote as expired |
          enum:
            - TRANSACTION_NOT_PENDING_PLATFORM_APPROVAL
            - TRANSACTION_NOT_CANCELLABLE
            - UMA_ADDRESS_EXISTS
            - EMAIL_OTP_EMAIL_ALREADY_EXISTS
            - PASSKEY_ALREADY_ENROLLED
            - SCA_SESSION_REQUIRED
            - BENEFICIARY_NOT_TRUSTED
            - BENEFICIARY_TRUSTED
            - BANK_ACCOUNT_VALIDATION_PENDING
            - INVALID_STATE_TRANSITION
            - CARD_ALREADY_CLOSED
            - CARD_NOT_MUTABLE
            - PIN_CHANGE_REQUIRED
            - AGENT_CARD_EXISTS
            - PURCHASE_CARD_LIMIT_REACHED
            - 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_PHONE_NUMBER_ALREADY_EXISTS
            - STABLECOIN_SYMBOL_ALREADY_EXISTS
            - STABLECOIN_TOKEN_IDENTIFIER_ALREADY_EXISTS
            - WALLET_NOT_PROVISIONED
            - PAYMENT_DOCUMENT_ALREADY_USED
            - INVOICE_ALREADY_USED
        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
    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
    EmailOtpCredentialCreateRequest:
      title: Email OTP Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/EmailOtpCredentialCreateRequestFields'
    SmsOtpCredentialCreateRequest:
      title: SMS OTP Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/SmsOtpCredentialCreateRequestFields'
    OauthCredentialCreateRequest:
      title: OAuth Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/OauthCredentialCreateRequestFields'
    PasskeyCredentialCreateRequest:
      title: Passkey Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/PasskeyCredentialCreateRequestFields'
    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
        adminReference:
          type: string
          description: >-
            The platform's reference for the administrator this credential
            belongs to, for a credential added with `POST
            /auth/admin-credentials`. Absent for the account owner's
            credentials.
          example: member_8f2c1a
        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'
    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'
    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.
    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
    RefusalReason:
      type: string
      description: >
        Why Grid refused the request, as `details.refusalReason`. Several
        refusals

        share one `code`, and `reason` is a message that may be reworded, so
        branch

        on this value rather than on `reason`. Treat it as an open set, not a
        fixed

        list: Grid may add values, and an unknown one is a refusal without a
        known

        cause.


        | Refusal reason | Status | Description |

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

        | `AGENT_API_DISABLED` | 403 | The agent API is not enabled for the
        platform |

        | `AGENT_CUSTOMER_INACTIVE` | 403 | The agent's customer is not active |

        | `AGENT_INACTIVE` | 403 | The agent is paused or deleted |

        | `AGENT_NO_DELEGATED_KEY` | 403 | The agent holds no active delegated
        key for the source wallet |

        | `AGENT_PERMISSION_REQUIRED` | 403 | The agent's policy lacks a
        permission this call needs. `details.permission` names it |

        | `AGENT_ACCOUNT_NOT_ALLOWED` | 403 | The agent's policy does not allow
        the source account or funding source |

        | `AGENT_DEPOSIT_ACCOUNT_NOT_ALLOWED` | 403 | The agent's policy does
        not allow deposits to this account |

        | `AGENT_POLICY_DENIED` | 403 | The agent's policy denied executing the
        quote. `details.policyDenialReason` says which rule |

        | `AGENT_QUOTE_HELD` | 403 | The quote cannot be sent and is held for
        manual follow-up |

        | `AGENT_CARDS_DISABLED` | 403 | Agent cards are not enabled for the
        platform |

        | `AGENT_CARDS_CUSTOMER_NOT_VERIFIED` | 403 | Agent cards need a
        verified individual customer |

        | `AGENT_CARD_HANDOVER_DISABLED` | 403 | Handing an agent's card over to
        its customer is not enabled for the platform |

        | `AGENT_PURCHASE_CARDS_DISABLED` | 403 | Agent purchase cards are not
        enabled for the platform |

        | `AGENT_RECURRING_PURCHASE_CARDS_DISABLED` | 403 | Agent recurring
        purchase cards are not enabled for the platform |

        | `AGENT_PURCHASE_CARD_POLICY_DENIED` | 403 | The agent's policy denied
        the purchase card. `details.policyDenialReason` says which rule |

        | `STORED_CARDS_DISABLED` | 403 | Stored cards are not enabled for the
        platform |

        | `CARDS_DISABLED` | 403 | Cards are not enabled for the platform |

        | `CARDHOLDER_INFO_MISSING` | 400 | The cardholder record lacks fields a
        card needs. `details.missingFields` lists them; supply them through the
        customers API |

        | `CARDHOLDER_BANK_ACCOUNT_NOT_OPEN` | 400 | The cardholder's USD bank
        account is not open yet, so no card can be issued until it is |

        | `CARDHOLDER_COUNTRY_NOT_SUPPORTED` | 400 | The cardholder's address is
        outside the United States, where agent cards are not offered yet |

        | `CARDHOLDER_HOLDS_CARD` | 409 | The cardholder already holds a card
        and the platform allows one per cardholder. Close it before issuing
        another |

        | `INVOICE_SUB_SAT_AMOUNT` | 400 | The Lightning invoice's amount is not
        a whole number of satoshis |

        | `INVOICE_FUNDS_DEPOSIT` | 400 | The Lightning invoice was issued by
        Lightspark to fund a deposit and cannot be paid with a send |

        | `INVOICE_FUNDS_OWN_DEPOSIT` | 400 | The Lightning invoice funds a
        deposit of the sender's own. Quote a transfer between the customer's
        accounts instead |
      example: AGENT_POLICY_DENIED
    AgentPolicyDenialReason:
      type: string
      description: >
        Which agent policy rule denied executing a quote or issuing a purchase
        card.

        Sent as `details.policyDenialReason` with refusal reason

        `AGENT_POLICY_DENIED` or `AGENT_PURCHASE_CARD_POLICY_DENIED`. Treat it
        as an

        open set, not a fixed list: Grid may add values.


        | Reason | Description |

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

        | `INVALID_AMOUNT` | The amount is not one the policy can evaluate |

        | `AGENT_PAUSED` | The agent is paused |

        | `PERMISSION_MISSING` | The policy lacks `EXECUTE_QUOTES` |

        | `ACCOUNT_NOT_ALLOWED` | The policy does not allow the source account |

        | `PER_TRANSACTION_LIMIT_EXCEEDED` | The amount exceeds the
        per-transaction limit |

        | `DAILY_LIMIT_EXCEEDED` | The amount would exceed the daily spending
        limit |

        | `MONTHLY_LIMIT_EXCEEDED` | The amount would exceed the monthly
        spending limit |

        | `DAILY_TRANSACTION_LIMIT_EXCEEDED` | The agent has made its daily
        number of transactions |

        | `POOL_LIMIT_EXCEEDED` | The amount would exceed a limit of the agent's
        spending pool |

        | `POOL_PAYMENT_TYPE_NOT_ALLOWED` | The agent's spending pool does not
        allow this kind of payment |
      example: DAILY_LIMIT_EXCEEDED
    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
    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'
    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
    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'
    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
    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
  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/redeem`. Agent credentials are user-scoped: all
        requests are automatically bound to the agent's associated customer and
        subject to the agent's policy.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.