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

# Re-issue an authentication credential challenge

> Re-issue the challenge for an existing authentication credential.

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

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

For `PASSKEY` credentials, this issues a fresh Grid reauthentication challenge. The request body must carry the client's ephemeral `clientPublicKey` so Grid can bake it into the Turnkey session-creation payload the returned challenge is computed from — this seals the resulting session signing key to the client. The response is a `PasskeyAuthChallenge` — the passkey auth method fields plus the WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`. The `challenge` value is the lowercase hex-encoded SHA-256 digest of the canonical Turnkey session-creation body, not a base64url string. The client base64url-decodes `credentialId` for `allowCredentials[].id` and UTF-8 encodes `challenge` (for example, `new TextEncoder().encode(challenge)`) as the WebAuthn challenge in `navigator.credentials.get()`, then submits the resulting assertion to `POST /auth/credentials/{id}/verify` with `Request-Id: <requestId>` to receive a session.




## OpenAPI

````yaml https://app.stainless.com/api/spec/documented/grid/openapi.documented.yml post /auth/credentials/{id}/challenge
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}/challenge:
    post:
      tags:
        - Embedded Wallet Auth
      summary: Re-issue an authentication credential challenge
      description: >
        Re-issue the challenge for an existing authentication credential.


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


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


        For `PASSKEY` credentials, this issues a fresh Grid reauthentication
        challenge. The request body must carry the client's ephemeral
        `clientPublicKey` so Grid can bake it into the Turnkey session-creation
        payload the returned challenge is computed from — this seals the
        resulting session signing key to the client. The response is a
        `PasskeyAuthChallenge` — the passkey auth method fields plus the
        WebAuthn `credentialId`, new `challenge`, `requestId`, and `expiresAt`.
        The `challenge` value is the lowercase hex-encoded SHA-256 digest of the
        canonical Turnkey session-creation body, not a base64url string. The
        client base64url-decodes `credentialId` for `allowCredentials[].id` and
        UTF-8 encodes `challenge` (for example, `new
        TextEncoder().encode(challenge)`) as the WebAuthn challenge in
        `navigator.credentials.get()`, then submits the resulting assertion to
        `POST /auth/credentials/{id}/verify` with `Request-Id: <requestId>` to
        receive a session.
      operationId: challengeAuthCredential
      parameters:
        - name: id
          in: path
          description: >-
            The id of the authentication credential to re-challenge (the `id`
            field of the `AuthMethod` returned from `POST /auth/credentials`).
          required: true
          schema:
            type: string
      requestBody:
        description: >-
          Request body. Required when re-challenging a `PASSKEY` credential
          (must carry `clientPublicKey`). Ignored for `EMAIL_OTP`, where the
          credential type alone is sufficient — the OTP is delivered
          out-of-band. OAuth credentials do not use this endpoint.
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthCredentialChallengeRequest'
            examples:
              passkey:
                summary: Re-challenge a passkey credential
                value:
                  clientPublicKey: >-
                    04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2
              emailOtp:
                summary: Re-challenge an email-OTP credential (empty body)
                value: {}
      responses:
        '200':
          description: >-
            Challenge re-issued for the authentication credential. For
            `EMAIL_OTP` the body is a plain `AuthMethod` and a new OTP email has
            been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge`
            carrying the passkey `credentialId`, freshly issued `challenge`,
            `requestId`, and `expiresAt` required to complete reauthentication
            via `POST /auth/credentials/{id}/verify`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthCredentialResponseOneOf'
              examples:
                emailOtp:
                  summary: Email OTP challenge re-issued
                  value:
                    id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: EMAIL_OTP
                    nickname: example@lightspark.com
                    otpEncryptionTargetBundle: >-
                      '{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}'
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:35:00Z'
                passkey:
                  summary: Passkey reauthentication challenge issued
                  value:
                    id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
                    accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                    type: PASSKEY
                    credentialId: >-
                      KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
                    nickname: iPhone Face-ID
                    createdAt: '2026-04-08T15:30:01Z'
                    updatedAt: '2026-04-08T15:35:00Z'
                    challenge: >-
                      6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729
                    requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error400'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Authentication credential not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '429':
          description: >-
            Too many requests. Returned with `RATE_LIMITED` when challenge
            re-issues are requested more frequently than the credential
            challenge rate limit allows. Clients should back off and retry after
            the interval indicated by the `Retry-After` response header.
          headers:
            Retry-After:
              description: Number of seconds to wait before retrying the request.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error429'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
      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 authCredentialResponseOneOf = await
            client.auth.credentials.challenge('id', {
              clientPublicKey:
                '04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2',
            });


            console.log(authCredentialResponseOneOf);
        - 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_credential_response_one_of = client.auth.credentials.challenge(
                id="id",
                client_public_key="04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2",
            )
            print(auth_credential_response_one_of)
        - 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\tauthCredentialResponseOneOf, err := client.Auth.Credentials.Challenge(\n\t\tcontext.TODO(),\n\t\t\"id\",\n\t\tgrid.AuthCredentialChallengeParams{},\n\t)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tfmt.Printf(\"%+v\\n\", authCredentialResponseOneOf)\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.AuthCredentialResponseOneOf

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


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

                val authCredentialResponseOneOf: AuthCredentialResponseOneOf = client.auth().credentials().challenge("id")
            }
        - lang: Ruby
          source: >-
            require "grid"


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


            auth_credential_response_one_of =
            lightspark_grid.auth.credentials.challenge("id")


            puts(auth_credential_response_one_of)
        - 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 {
              $authCredentialResponseOneOf = $client->auth->credentials->challenge(
                'id',
                clientPublicKey: '04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2',
              );

              var_dump($authCredentialResponseOneOf);
            } catch (APIException $e) {
              echo $e->getMessage();
            }
        - lang: C#
          source: >-
            using System;

            using Grid;

            using Grid.Models.Auth.Credentials;


            LightsparkGridClient client = new();


            CredentialChallengeParams parameters = new() { ID = "id" };


            var authCredentialResponseOneOf = await
            client.Auth.Credentials.Challenge(parameters);


            Console.WriteLine(authCredentialResponseOneOf);
        - lang: CLI
          source: |-
            grid auth:credentials challenge \
              --username 'My Username' \
              --password 'My Password' \
              --id id
components:
  schemas:
    AuthCredentialChallengeRequest:
      title: Auth Credential Challenge Request
      description: >-
        Request body for `POST /auth/credentials/{id}/challenge`. Required when
        re-challenging a `PASSKEY` credential — must carry `clientPublicKey` so
        Grid can bake it into the Turnkey session-creation payload the returned
        challenge is computed from. Ignored for `EMAIL_OTP`, where the
        credential type alone is sufficient because the OTP is delivered
        out-of-band. OAuth credentials do not use this endpoint; authenticate or
        reauthenticate them with `POST /auth/credentials/{id}/verify`.
      type: object
      properties:
        clientPublicKey:
          type: string
          pattern: ^04[0-9a-fA-F]{128}$
          minLength: 130
          maxLength: 130
          description: >-
            Required for `PASSKEY` credentials. Client-generated P-256 public
            key, hex-encoded in uncompressed SEC1 format (`04` 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
            bakes this key into the Turnkey session-creation payload that the
            returned `challenge` is computed from, so the resulting session
            signing key is sealed to the client. Ignored for `EMAIL_OTP`.
          example: >-
            04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2
    AuthCredentialResponseOneOf:
      title: Auth Credential Response
      description: >-
        Discriminated response shape returned from `POST
        /auth/credentials/{id}/challenge`. For `EMAIL_OTP` credentials the body
        is a plain `AuthMethod` (wrapped as `AuthMethodResponse` to disambiguate
        the oneOf). For `PASSKEY` credentials the body is a
        `PasskeyAuthChallenge` — the passkey auth method fields plus the
        WebAuthn `credentialId`, Grid-issued `challenge`, `requestId`, and
        `expiresAt` that drive the subsequent assertion. OAuth credentials do
        not use the challenge endpoint. Registration responses from `POST
        /auth/credentials` use the simpler `AuthMethodResponse` shape directly
        for all three credential types.
      oneOf:
        - $ref: '#/components/schemas/AuthMethodResponse'
        - $ref: '#/components/schemas/PasskeyAuthChallenge'
      discriminator:
        propertyName: type
        mapping:
          EMAIL_OTP:
            $ref: '#/components/schemas/AuthMethodResponse'
          PASSKEY:
            $ref: '#/components/schemas/PasskeyAuthChallenge'
    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
    AuthMethodResponse:
      title: Auth Method Response
      description: >-
        Strict wrapper around `AuthMethod`. Used directly as the registration
        response on `POST /auth/credentials` (all three credential types) and
        inside `AuthCredentialResponseOneOf` for the `EMAIL_OTP` branch 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` 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` responses that initiate
                or reissue an OTP challenge, such as `POST
                /auth/credentials/{id}/challenge` and the add-EMAIL_OTP
                signed-retry response. 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}`; 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 sign the `verificationToken` returned on the subsequent
                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
    PasskeyAuthChallenge:
      title: Passkey Auth Challenge
      description: >-
        Extended `AuthMethod` shape returned for `PASSKEY` credentials from
        `POST /auth/credentials/{id}/challenge`. Includes the WebAuthn
        `credentialId` needed to target the passkey, plus the Grid-issued
        `challenge`, corresponding `requestId`, and challenge `expiresAt`. The
        `challenge` value is the lowercase hex-encoded SHA-256 digest of the
        canonical Turnkey session-creation request body, not a base64url string.
        The client UTF-8 encodes this string as the WebAuthn challenge and signs
        it with the passkey to produce the assertion submitted to `POST
        /auth/credentials/{id}/verify`.
      allOf:
        - $ref: '#/components/schemas/AuthMethod'
        - type: object
          required:
            - credentialId
            - challenge
            - requestId
            - expiresAt
          properties:
            credentialId:
              type: string
              description: >-
                Base64url-encoded WebAuthn credential identifier for this
                passkey. Corresponds to `PublicKeyCredential.rawId`; pass this
                value as `allowCredentials[].id` when requesting a passkey
                assertion for this auth method.
              example: >-
                KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
            challenge:
              type: string
              description: >-
                Lowercase hex-encoded SHA-256 digest of the canonical Turnkey
                session-creation request body for the pending passkey
                authentication. Do not base64url-decode this field; pass UTF-8
                bytes of the string (for example, `new
                TextEncoder().encode(challenge)`) as the WebAuthn challenge to
                `navigator.credentials.get()`. Single-use; a new challenge is
                issued on the next call to `POST
                /auth/credentials/{id}/challenge`.
              example: 6b35a4c41d9aa7a2a0e742f9f9e7a1c2d65a2db33a3fb748f6d4f1ce78d9a729
            requestId:
              type: string
              description: >-
                Grid-issued `Request:<uuid>` identifier for this pending passkey
                authentication request. Echo this value exactly as the
                `Request-Id` header on the subsequent `POST
                /auth/credentials/{id}/verify` call so Grid can correlate the
                assertion with the issued challenge.
              example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
            expiresAt:
              type: string
              format: date-time
              description: >-
                Timestamp after which the issued challenge is no longer valid.
                The assertion must reach `POST /auth/credentials/{id}/verify`
                before this time; otherwise the client must request a fresh
                challenge via `POST /auth/credentials/{id}/challenge`.
              example: '2026-04-08T15:35:00Z'
    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'
    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.
  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.

````