> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-04-30-docs-add-grid-tutorial-skill-interactive-zero-t.mintlify.site/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.

**First credential on an internal account**

If the target internal account does not yet have any authentication credential registered, call this endpoint with the credential details. The response is `201` with the created `AuthMethod`. For `EMAIL_OTP` credentials, this call also triggers a one-time password email to the address on the customer record tied to the internal account; the credential must be activated via `POST /auth/credentials/{id}/verify` before it can sign requests. For `OAUTH` credentials, the supplied `oidcToken` is validated inline against the issuer's `.well-known` OpenID configuration (the token's `iat` must be less than 60 seconds before the request); activation still happens via `POST /auth/credentials/{id}/verify`. For `PASSKEY` credentials, the client completes a WebAuthn registration (`navigator.credentials.create()`) using a `challenge` issued by the platform backend and submits the resulting `attestation` here. The registration response is a plain `AuthMethod` (no inline authentication challenge). To produce the first session, the client follows registration with two further calls: `POST /auth/credentials/{id}/challenge` (carrying the client's ephemeral `clientPublicKey`) returns a Grid-issued WebAuthn challenge plus `requestId`, and `POST /auth/credentials/{id}/verify` (with `Request-Id: <requestId>`) consumes the resulting assertion and issues the session. The same two-step pattern is used on every subsequent reauthentication. Only one `PASSKEY` credential is supported per internal account in v1.

**Adding an additional credential**

Registering an additional credential against an internal account that already has one requires a signature from an existing verified credential. Call this endpoint with the new credential's details; if an existing credential is already registered on the internal account the response is `202` with a `payloadToSign` and a `requestId`. Use the session API keypair of an existing verified credential on the same internal account (decrypted client-side from its `encryptedSessionSigningKey`) 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 `EMAIL_OTP`, the OTP email is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`.




## OpenAPI

````yaml https://app.stainless.com/api/spec/documented/grid/openapi.documented.yml 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://grid.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: []
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.
paths:
  /auth/credentials:
    post:
      tags:
        - Embedded Wallet Auth
      summary: Create an authentication credential
      description: >
        Register an authentication credential for an Embedded Wallet customer.


        **First credential on an internal account**


        If the target internal account does not yet have any authentication
        credential registered, call this endpoint with the credential details.
        The response is `201` with the created `AuthMethod`. For `EMAIL_OTP`
        credentials, this call also triggers a one-time password email to the
        address on the customer record tied to the internal account; the
        credential must be activated via `POST /auth/credentials/{id}/verify`
        before it can sign requests. For `OAUTH` credentials, the supplied
        `oidcToken` is validated inline against the issuer's `.well-known`
        OpenID configuration (the token's `iat` must be less than 60 seconds
        before the request); activation still happens via `POST
        /auth/credentials/{id}/verify`. For `PASSKEY` credentials, the client
        completes a WebAuthn registration (`navigator.credentials.create()`)
        using a `challenge` issued by the platform backend and submits the
        resulting `attestation` here. The registration response is a plain
        `AuthMethod` (no inline authentication challenge). To produce the first
        session, the client follows registration with two further calls: `POST
        /auth/credentials/{id}/challenge` (carrying the client's ephemeral
        `clientPublicKey`) returns a Grid-issued WebAuthn challenge plus
        `requestId`, and `POST /auth/credentials/{id}/verify` (with `Request-Id:
        <requestId>`) consumes the resulting assertion and issues the session.
        The same two-step pattern is used on every subsequent reauthentication.
        Only one `PASSKEY` credential is supported per internal account in v1.


        **Adding an additional credential**


        Registering an additional credential against an internal account that
        already has one requires a signature from an existing verified
        credential. Call this endpoint with the new credential's details; if an
        existing credential is already registered on the internal account the
        response is `202` with a `payloadToSign` and a `requestId`. Use the
        session API keypair of an existing verified credential on the same
        internal account (decrypted client-side from its
        `encryptedSessionSigningKey`) 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 `EMAIL_OTP`, the OTP email 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 when registering
            an additional credential on an internal account that already has
            one; ignored when the internal account has no existing credentials.
          schema:
            type: string
          example: >-
            eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzaWduYXR1cmUiOiIzMDQ1MDIyMTAwLi4uIiwic2NoZW1lIjoiUDI1Nl9FQ0RTQV9TSEEyNTYifQ
        - name: Request-Id
          in: header
          required: false
          description: >-
            The `requestId` returned in a prior `202` response, echoed back on
            the signed retry so the server can correlate it with the issued
            challenge. Required on the signed retry when registering an
            additional credential; must be paired with `Grid-Wallet-Signature`.
          schema:
            type: string
          example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthCredentialCreateRequestOneOf'
            examples:
              emailOtp:
                summary: Register an email OTP credential
                value:
                  type: EMAIL_OTP
                  accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
              oauth:
                summary: Register an OAuth credential
                value:
                  type: OAUTH
                  accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
                  oidcToken: >-
                    eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.signature
              passkey:
                summary: Register 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:
        '201':
          description: >-
            Authentication credential created successfully. The body is the
            created `AuthMethod` for all three credential types. 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
                    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: >-
            An existing authentication credential is already registered on the
            internal account. The response contains `payloadToSign` plus a
            `requestId`. Build an API-key stamp over `payloadToSign` with the
            session API keypair of an existing verified credential on the same
            internal account, 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: >-
                      {"requestId":"7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21","type":"EMAIL_OTP","accountId":"InternalAccount:01HF3Z4QWERTY","expiresAt":"2026-04-08T15:35:00Z"}
                    requestId: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
                oauth:
                  summary: Additional OAuth credential challenge
                  value:
                    type: OAUTH
                    payloadToSign: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg==
                    requestId: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
                    expiresAt: '2026-04-08T15:35:00Z'
                passkey:
                  summary: Additional passkey credential challenge
                  value:
                    type: PASSKEY
                    payloadToSign: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg==
                    requestId: 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 on an internal account
            that already has one — only one email OTP credential is supported
            per internal account at this time. Returned with
            `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a `PASSKEY`
            credential on an internal account that already has one — only one
            passkey credential is supported per internal account in v1.
          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, or when the
            `Request-Id` does not match an unexpired pending challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error401'
        '404':
          description: Internal account not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error404'
        '500':
          description: Internal service error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error500'
      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 authMethod = await client.auth.credentials.create({
              AuthCredentialCreateRequest: {
                accountId: 'InternalAccount:019542f5-b3e7-1d02-0000-000000000002',
                type: 'EMAIL_OTP',
              },
            });

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

            client = LightsparkGrid(
                username=os.environ.get("GRID_CLIENT_ID"),  # This is the default and can be omitted
                password=os.environ.get("GRID_CLIENT_SECRET"),  # This is the default and can be omitted
            )
            auth_method = client.auth.credentials.create(
                auth_credential_create_request={
                    "account_id": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
                    "type": "EMAIL_OTP",
                },
            )
            print(auth_method.id)
        - 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.AuthMethod

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


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

                val params: CredentialCreateParams.AuthCredentialCreateRequest.EmailOtpCredentialCreateRequest = CredentialCreateParams.AuthCredentialCreateRequest.EmailOtpCredentialCreateRequest.builder()
                    .accountId("InternalAccount:019542f5-b3e7-1d02-0000-000000000002")
                    .type(CredentialCreateParams.AuthCredentialCreateRequest.EmailOtpCredentialCreateRequest.Type.EMAIL_OTP)
                    .build()
                val authMethod: AuthMethod = client.auth().credentials().create(params)
            }
components:
  schemas:
    AuthCredentialCreateRequestOneOf:
      oneOf:
        - $ref: '#/components/schemas/EmailOtpCredentialCreateRequest'
        - $ref: '#/components/schemas/OauthCredentialCreateRequest'
        - $ref: '#/components/schemas/PasskeyCredentialCreateRequest'
      discriminator:
        propertyName: type
        mapping:
          EMAIL_OTP:
            $ref: '#/components/schemas/EmailOtpCredentialCreateRequest'
          OAUTH:
            $ref: '#/components/schemas/OauthCredentialCreateRequest'
          PASSKEY:
            $ref: '#/components/schemas/PasskeyCredentialCreateRequest'
    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` and `OAUTH`
        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.
      allOf:
        - $ref: '#/components/schemas/AuthMethod'
      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),
        and `DELETE /auth/sessions/{id}` (revoking a session). Carries the
        signing fields from `SignedRequestChallenge` plus the `type` of the
        authentication credential involved (being added, being revoked, or that
        issued the session being revoked). 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.
      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`), the credential type
                being revoked (`DELETE /auth/credentials/{id}`), or the type of
                credential that issued the session being revoked (`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 is
            already registered on the target internal account; only one passkey
            credential is supported per internal account in v1 |
          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 |
          enum:
            - UNAUTHORIZED
            - INVALID_SIGNATURE
        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 |
          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
        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
    EmailOtpCredentialCreateRequest:
      title: Email OTP Credential Create Request
      allOf:
        - $ref: '#/components/schemas/AuthCredentialCreateRequest'
        - $ref: '#/components/schemas/EmailOtpCredentialCreateRequestFields'
    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'
        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 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 revocation, session revocation, wallet
        export, 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: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg==
        requestId:
          type: string
          description: >-
            Unique identifier for this request. Must be echoed in the
            `Request-Id` header on the signed retry so the server can correlate
            the retry with the issued challenge.
          example: 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.
    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.
    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).
            Grid fetches the issuer's signing key from the `iss` claim's
            `.well-known` OpenID configuration and verifies the token signature.
            The token's `iat` claim must be less than 60 seconds before the
            request timestamp.
          example: >-
            eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.signature
    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"). 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'
    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>`

````