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

# Verify an authentication credential

> Complete the verification step for a previously created authentication credential and issue a session.

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

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

On success for `OAUTH` and `PASSKEY`, and on the signed retry for `EMAIL_OTP`, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for `EMAIL_OTP` the client already holds the session signing key (the TEK private key it generated) and that field is omitted from the response. The `expiresAt` timestamp marks when the session expires.




## OpenAPI

````yaml https://app.stainless.com/api/spec/documented/grid/openapi.documented.yml post /auth/credentials/{id}/verify
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: 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: >-
      Endpoints for transferring funds between internal and external accounts
      with the same currency
  - name: Cross-Currency Transfers
    description: Endpoints for creating and confirming quotes for cross-currency transfers
  - 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 card funding sources, and list card
      transactions.
paths:
  /auth/credentials/{id}/verify:
    post:
      tags:
        - Embedded Wallet Auth
      summary: Verify an authentication credential
      description: >
        Complete the verification step for a previously created authentication
        credential and issue a session.


        For `EMAIL_OTP` credentials, submit the `encryptedOtpBundle` produced by
        HPKE-encrypting `{otp_code, public_key}` under the
        `otpEncryptionTargetBundle` returned from registration when present, or
        from `POST /auth/credentials/{id}/challenge` when registration omitted
        it or the OTP must be reissued. The server is a pass-through and never
        sees the plaintext OTP code. On success the response is `202` with a
        `payloadToSign` carrying the `verificationToken` bound to the client's
        TEK public key — sign that token with the matching TEK private key, then
        retry the same request with the full stamp in `Grid-Wallet-Signature`
        and the `requestId` echoed in `Request-Id`. The signed retry returns
        `200` with the issued `AuthSession`. The TEK public key becomes the
        session API key on successful completion.

        In sandbox mode, the EMAIL_OTP flow runs real HPKE end-to-end against a
        sandbox enclave keypair — clients build a real `encryptedOtpBundle`
        against the sandbox `otpEncryptionTargetBundle` and sign a real
        `verificationToken` with their TEK keypair. The only sandbox shortcut is
        the magic OTP code (`"000000"`) the user "receives" instead of a real
        email delivery.


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


        On success for `OAUTH` and `PASSKEY`, and on the signed retry for
        `EMAIL_OTP`, the response contains an `AuthSession`. For `OAUTH` and
        `PASSKEY` the session signing key is delivered as
        `encryptedSessionSigningKey` (HPKE-sealed to the supplied
        `clientPublicKey`); for `EMAIL_OTP` the client already holds the session
        signing key (the TEK private key it generated) and that field is omitted
        from the response. The `expiresAt` timestamp marks when the session
        expires.
      operationId: verifyAuthCredential
      parameters:
        - name: id
          in: path
          description: >-
            The id of the authentication credential to verify (the `id` field of
            the `AuthMethod` returned from `POST /auth/credentials`).
          required: true
          schema:
            type: string
        - name: Grid-Wallet-Signature
          in: header
          required: false
          description: >-
            Full API-key stamp built over the prior `payloadToSign` with the TEK
            (Target Encryption Key) keypair the client generated for this login.
            Required on the signed retry that completes an `EMAIL_OTP`
            verification. Not used by `OAUTH` or `PASSKEY` verification, which
            complete in a single call.
          schema:
            type: string
          example: >-
            eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
        - name: Request-Id
          in: header
          required: false
          description: >-
            The `requestId` returned in a prior `202` response from this
            endpoint, echoed back exactly here so the server can correlate the
            signed retry with the issued challenge. Required on the signed retry
            that completes an `EMAIL_OTP` verification; must be paired with
            `Grid-Wallet-Signature`. For `PASSKEY` verification, the `requestId`
            issued from `POST /auth/credentials/{id}/challenge` is echoed here
            instead so the server can correlate the assertion with the pending
            challenge.
          schema:
            type: string
          example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthCredentialVerifyRequestOneOf'
            examples:
              emailOtp:
                summary: Verify an email OTP credential (first leg)
                value:
                  type: EMAIL_OTP
                  encryptedOtpBundle: >-
                    {"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}
              emailOtpSignedRetry:
                summary: Signed retry completing an email OTP verification
                description: >-
                  Same request body as the first leg, plus the
                  `Grid-Wallet-Signature` and `Request-Id` headers carrying the
                  stamp over the `verificationToken` and the `requestId` from
                  the prior `202` response.
                value:
                  type: EMAIL_OTP
                  encryptedOtpBundle: >-
                    {"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}
              oauth:
                summary: Verify an OAuth credential
                value:
                  type: OAUTH
                  oidcToken: >-
                    eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q
                  clientPublicKey: >-
                    04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2
              passkey:
                summary: Verify a passkey credential
                value:
                  type: PASSKEY
                  assertion:
                    credentialId: >-
                      KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
                    clientDataJson: >-
                      eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0
                    authenticatorData: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA
                    signature: >-
                      MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900
      responses:
        '200':
          description: Authentication credential verified and session issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSession'
        '202':
          description: >-
            Verification challenge issued. Returned only for `EMAIL_OTP`
            credentials, on the first leg of the secure OTP login flow. Build an
            API-key stamp over `payloadToSign` (the `verificationToken`) with
            the TEK keypair the client generated for this login, then resubmit
            the same request with that full stamp as `Grid-Wallet-Signature` and
            `requestId` echoed as `Request-Id` to receive the issued session on
            the signed retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSignedRequestChallenge'
              examples:
                emailOtp:
                  summary: Email OTP verification challenge (sign and retry)
                  value:
                    type: EMAIL_OTP
                    payloadToSign: >-
                      eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ.eyJzdWIiOiJUWnk2NkVPa1RGYTd2NkpXZ0VxaVgyZGFXOENXc2pMQzVDVU9aRUlGY3hzIiwiaWF0IjoxNzc5NDA3MjIxLCJleHAiOjE3Nzk0MTA4MjF9.gKX9MWYGkw8Y55bgzsgrRftvUHFruIe8yu0w9Kpjp5qnrZnXcTV71WVoltGPsr015IY_oRTOkIFLHmiGNG9zBw
                    requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: >-
            Unauthorized. Returned for an invalid or expired OTP (`EMAIL_OTP`),
            for an OIDC token whose signature, issuer, identity, nonce, or `iat`
            freshness check failed (`OAUTH`), or for a WebAuthn assertion whose
            signature, challenge, or credential match failed (`PASSKEY`). Also
            returned for `PASSKEY` when `Request-Id` is missing, does not match
            an unexpired pending challenge for this credential, or was already
            consumed. For `EMAIL_OTP` signed retries, returned when
            `Grid-Wallet-Signature` is missing, malformed, signed by a public
            key that does not match the one bound into the `verificationToken`,
            or when `Request-Id` does not match an unexpired pending
            verification challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Authentication credential not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '429':
          description: >-
            Too many requests. Returned with `RATE_LIMITED` when verification
            attempts for this credential happen too frequently (for example,
            repeated bad OTPs or rapid-fire reauthentication retries). Clients
            should back off and retry after the interval indicated by the
            `Retry-After` response header.
          headers:
            Retry-After:
              description: Number of seconds to wait before retrying the request.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
      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 authSession = await client.auth.credentials.verify('id', {
            AuthCredentialVerifyRequest: {} });


            console.log(authSession);
        - 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
            )
            auth_session = client.auth.credentials.verify(
                id="id",
                auth_credential_verify_request={},
            )
            print(auth_session)
        - 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\tauthSession, err := client.Auth.Credentials.Verify(\n\t\tcontext.TODO(),\n\t\t\"id\",\n\t\tgrid.AuthCredentialVerifyParams{\n\t\t\tAuthCredentialVerifyRequest: map[string]any{},\n\t\t},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf(\"%+v\\n\", authSession)\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.auth.credentials.AuthCredentialVerifyRequestOneOf

            import com.lightspark.grid.models.auth.credentials.AuthSession

            import
            com.lightspark.grid.models.auth.credentials.CredentialVerifyParams


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

                val params: CredentialVerifyParams = CredentialVerifyParams.builder()
                    .id("id")
                    .authCredentialVerifyRequest(AuthCredentialVerifyRequestOneOf.builder().build())
                    .build()
                val authSession: AuthSession = client.auth().credentials().verify(params)
            }
        - lang: Ruby
          source: >-
            require "grid"


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


            auth_session = lightspark_grid.auth.credentials.verify("id",
            auth_credential_verify_request: {})


            puts(auth_session)
        - 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 {
              $authSession = $client->auth->credentials->verify(
                'id',
                authCredentialVerifyRequest: (object) [],
                gridWalletSignature: 'eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9',
                requestID: 'Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21',
              );

              var_dump($authSession);
            } catch (APIException $e) {
              echo $e->getMessage();
            }
        - lang: C#
          source: |-
            using System;
            using System.Text.Json;
            using Grid;
            using Grid.Models.Auth.Credentials;

            LightsparkGridClient client = new();

            CredentialVerifyParams parameters = new()
            {
                ID = "id",
                AuthCredentialVerifyRequest = JsonSerializer.Deserialize<JsonElement>("{}"),
            };

            var authSession = await client.Auth.Credentials.Verify(parameters);

            Console.WriteLine(authSession);
        - lang: CLI
          source: |-
            grid auth:credentials verify \
              --username 'My Username' \
              --password 'My Password' \
              --id id \
              --auth-credential-verify-request '{}'
components:
  schemas:
    AuthCredentialVerifyRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/EmailOtpCredentialVerifyRequest'
        - $ref: '#/components/schemas/OauthCredentialVerifyRequest'
        - $ref: '#/components/schemas/PasskeyCredentialVerifyRequest'
      discriminator:
        propertyName: type
        mapping:
          EMAIL_OTP:
            $ref: '#/components/schemas/EmailOtpCredentialVerifyRequest'
          OAUTH:
            $ref: '#/components/schemas/OauthCredentialVerifyRequest'
          PASSKEY:
            $ref: '#/components/schemas/PasskeyCredentialVerifyRequest'
    AuthSession:
      title: Authentication Session
      description: >-
        An authentication session on an Embedded Wallet internal account.
        Returned from `GET /auth/sessions` (list) and `POST
        /auth/credentials/{id}/verify` (on credential verification) or `POST
        /auth/sessions/{id}/refresh` (on mid-session refresh). Only
        session-issuing responses include `encryptedSessionSigningKey` — it is
        delivered exactly once at the moment the session is issued and is never
        returned by the list endpoint.
      allOf:
        - $ref: '#/components/schemas/AuthMethod'
        - type: object
          required:
            - id
            - expiresAt
          properties:
            id:
              type: string
              description: >-
                System-generated unique identifier for the session. Pass this
                value to `DELETE /auth/sessions/{id}` to revoke the session
                before `expiresAt`. Overrides the `id` inherited from
                `AuthMethod` so this response identifies the session rather than
                the authenticating credential.
              example: Session:019542f5-b3e7-1d02-0000-000000000003
            encryptedSessionSigningKey:
              type: string
              description: >-
                HPKE-encrypted session signing key, sealed to the
                `clientPublicKey` supplied on the verification or refresh
                request. Encoded as a base58check string: the decoded payload is
                a 33-byte compressed P-256 encapsulated public key followed by
                AES-256-GCM ciphertext. The client decrypts this key with its
                private key and uses it to sign subsequent Embedded Wallet
                requests until `expiresAt`.


                Returned only by session-issuing responses for `OAUTH` and
                `PASSKEY` credentials. `EMAIL_OTP` sessions omit this field —
                the client generates a TEK keypair before verification and
                retains the private key throughout, so the server has nothing to
                deliver. Always omitted from list responses (`GET
                /auth/sessions`) since Grid does not retain the plaintext key
                after the client has decrypted it.
              example: >-
                w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf
            expiresAt:
              type: string
              format: date-time
              description: >-
                Timestamp after which the session is no longer valid and the
                `encryptedSessionSigningKey` must not be used to sign further
                requests.
              example: '2026-04-09T15:30:01Z'
    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`
        branch of `POST /auth/credentials/{id}/verify` (the secure OTP login
        flow, where the client submits an `encryptedOtpBundle` and receives a
        `verificationToken` to sign for the second-leg 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 the `EMAIL_OTP` verify retry, 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` 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:
        - 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 |

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

            | PASSKEY_CREDENTIAL_ALREADY_EXISTS | A PASSKEY credential with the
            same WebAuthn credentialId is already registered on the target
            internal account |
          enum:
            - INVALID_INPUT
            - 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
            - PASSKEY_CREDENTIAL_ALREADY_EXISTS
        message:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          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
    Error404:
      type: object
      required:
        - message
        - status
        - code
      properties:
        status:
          type: integer
          enum:
            - 404
          description: HTTP status code
        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 |
          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
        message:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details
          additionalProperties: true
    Error429:
      type: object
      required:
        - message
        - status
        - code
      properties:
        status:
          type: integer
          enum:
            - 429
          description: HTTP status code
        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 |
          enum:
            - RATE_LIMITED
        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
    EmailOtpCredentialVerifyRequest:
      title: Email OTP Credential Verify Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialVerifyRequest'
        - $ref: '#/components/schemas/EmailOtpCredentialVerifyRequestFields'
    OauthCredentialVerifyRequest:
      title: OAuth Credential Verify Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialVerifyRequest'
        - $ref: '#/components/schemas/OauthCredentialVerifyRequestFields'
    PasskeyCredentialVerifyRequest:
      title: Passkey Credential Verify Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialVerifyRequest'
        - $ref: '#/components/schemas/PasskeyCredentialVerifyRequestFields'
    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 OAUTH credentials it is
            typically the email claim from the OIDC token; for PASSKEY
            credentials it is the validated nickname provided at registration
            time.
          example: example@lightspark.com
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp.
          example: '2026-04-08T15:30:01Z'
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp.
          example: '2026-04-08T15:35:00Z'
    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: >-
            Canonical payload for the retry authorization stamp. Build an
            API-key stamp over this exact value with the session API keypair,
            then 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
        - 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.

        - `PASSKEY`: A WebAuthn passkey bound to the user's device.
    AuthCredentialVerifyRequest:
      type: object
      required:
        - type
      properties:
        type:
          $ref: '#/components/schemas/AuthMethodType'
    EmailOtpCredentialVerifyRequestFields:
      type: object
      required:
        - type
        - encryptedOtpBundle
      description: >-
        Verify an email-OTP credential via the secure two-leg flow. The client
        HPKE-encrypts the OTP code (together with its public key) under the
        `otpEncryptionTargetBundle` returned from registration when present, or
        from `POST /auth/credentials/{id}/challenge` when registration omitted
        it or the OTP must be reissued, submits the result here, and receives
        `202` with a `payloadToSign` carrying a `verificationToken` bound to the
        client's public key. The client signs that token with the matching
        private key and retries this request with `Grid-Wallet-Signature` +
        `Request-Id` headers to obtain the session. Plaintext OTP codes are
        never sent over the wire.
      properties:
        type:
          type: string
          enum:
            - EMAIL_OTP
          description: Discriminator value identifying this as an email OTP verification.
        encryptedOtpBundle:
          type: string
          description: >-
            HPKE-sealed OTP attempt — the OTP code never reaches Grid in
            plaintext. The client generates a fresh ephemeral P-256 key pair
            (the session signing key pair it keeps once login completes),
            HPKE-encrypts `{otp_code, public_key}` (the code the user entered
            plus that key pair's public key) to the key in
            `otpEncryptionTargetBundle`, and submits the encrypted result here.
            The value is the `{encappedPublic, ciphertext}` JSON an HPKE library
            produces; the Global Accounts client-keys guide has a worked
            example.


            On success the response is `202` with a `payloadToSign` carrying a
            `verificationToken` bound to the public key sealed in this bundle.
            Sign that token with the matching private key, then retry this
            request with the full stamp in `Grid-Wallet-Signature` and the
            `requestId` in `Request-Id` to complete the flow and receive the
            session. The client keeps that private key as the session signing
            key, and its public key becomes the session API key.
          example: >-
            {"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}
    OauthCredentialVerifyRequestFields:
      type: object
      required:
        - type
        - oidcToken
        - clientPublicKey
      properties:
        type:
          type: string
          enum:
            - OAUTH
          description: Discriminator value identifying this as an OAuth verification.
        oidcToken:
          type: string
          description: >-
            OIDC ID token issued by the identity provider. For reauthentication
            after a prior session expired, supply a fresh token — the token's
            `iat` claim must be less than 60 seconds before the request
            timestamp. The token identity (`iss`, `aud`, and `sub`) must match
            the registered OAuth credential. In production, the provider
            signature is verified against the issuer's JWKS. In sandbox, the
            token must still be JWT-shaped with supported `iss`, non-empty `aud`
            and `sub`, numeric `iat` and `exp`, and a `nonce` equal to
            `sha256(clientPublicKey)`, but the signature segment may be a dummy
            value.
          example: >-
            eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q
        clientPublicKey:
          type: string
          description: >-
            Client-generated P-256 public key, hex-encoded in uncompressed SEC1
            format (0x04 prefix followed by the 32-byte X and 32-byte Y
            coordinates; 130 hex characters total). The matching private key
            must remain on the client. Grid encrypts the session signing key
            returned in the response to this public key. The key is ephemeral
            and one-time-use per verification request.
          example: >-
            04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2
    PasskeyCredentialVerifyRequestFields:
      type: object
      required:
        - type
        - assertion
      properties:
        type:
          type: string
          enum:
            - PASSKEY
          description: Discriminator value identifying this as a passkey verification.
        assertion:
          $ref: '#/components/schemas/PasskeyAssertion'
    PasskeyAssertion:
      title: Passkey Assertion
      type: object
      description: >-
        WebAuthn assertion returned by `navigator.credentials.get()`. In
        sandbox, Grid validates the assertion against the registered passkey
        credential so the client-side flow can match production. In production,
        Turnkey validates the WebAuthn assertion.
      required:
        - credentialId
        - clientDataJson
        - authenticatorData
        - signature
      properties:
        credentialId:
          type: string
          description: >-
            Base64url-encoded credential identifier returned during the WebAuthn
            assertion. Corresponds to `PublicKeyCredential.rawId`.
          example: >-
            KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
        clientDataJson:
          type: string
          description: >-
            Base64url-encoded JSON client data collected by the browser during
            the WebAuthn `navigator.credentials.get()` call. Corresponds to
            `AuthenticatorAssertionResponse.clientDataJSON` from the WebAuthn
            spec — Grid's field name is intentionally camelCased as
            `clientDataJson` (lowercase JSON) for consistency with the rest of
            the API; the value is the same bytes the browser returns. Contains
            the challenge, origin, and `type: "webauthn.get"`.
          example: >-
            eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0
        authenticatorData:
          type: string
          description: >-
            Base64url-encoded authenticator data returned by the authenticator
            during the assertion. Corresponds to
            `AuthenticatorAssertionResponse.authenticatorData`.
          example: PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA
        signature:
          type: string
          description: >-
            Base64url-encoded signature produced by the authenticator over
            `authenticatorData || SHA-256(clientDataJSON)`. Corresponds to
            `AuthenticatorAssertionResponse.signature`. The signature byte
            format is determined by the credential's public-key algorithm —
            DER-encoded ECDSA for ES256 (P-256, typical for passkeys), PKCS#1
            v1.5 for RS256, or a raw 64-byte signature for EdDSA.
          example: >-
            MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900
        userHandle:
          type: string
          description: >-
            Base64url-encoded user handle returned by the authenticator.
            Corresponds to `AuthenticatorAssertionResponse.userHandle`.
            Populated (and required by the WebAuthn spec) for discoverable
            credentials — resident keys used in the "Sign in with passkey"
            autofill flow — and typically present for passkey registrations.
            Omit this field entirely for non-discoverable credentials specified
            via `allowCredentials` where the authenticator returns no user
            handle.
          example: dXNlci1oYW5kbGUtZXhhbXBsZQ
  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.

````