openapi: 3.1.0
info:
title: Grid Agent Management Embedded Wallet Auth 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: 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:
summary: Create an authentication credential
description: 'Register an authentication credential for an Embedded Wallet customer.
Embedded Wallet internal accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on the account. Use this endpoint to add another credential (`SMS_OTP`, `OAUTH`, or `PASSKEY`), or to add `EMAIL_OTP` / `SMS_OTP` back after it has been removed. Only one `EMAIL_OTP` and one `SMS_OTP` credential are supported per internal account; multiple distinct `PASSKEY` credentials may be registered.
Adding a credential requires a signature from an existing verified credential on the same account. Call this endpoint with the new credential''s details to receive `202` with `payloadToSign` and `requestId`. Use the session API keypair of an existing verified credential (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 OTP credentials, the one-time password is triggered on the signed retry, and the credential must then be activated via `POST /auth/credentials/{id}/verify`.
'
operationId: createAuthCredential
tags:
- Embedded Wallet Auth
security:
- BasicAuth: []
parameters:
- name: Grid-Wallet-Signature
in: header
required: false
description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of an existing verified authentication credential on the target internal account. Required on the signed retry.
schema:
type: string
example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
- name: Request-Id
in: header
required: false
description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry when registering a credential; must be paired with `Grid-Wallet-Signature`.
schema:
type: string
example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AuthCredentialCreateRequestOneOf'
examples:
emailOtp:
summary: Add an email OTP credential
value:
type: EMAIL_OTP
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
smsOtp:
summary: Add an SMS OTP credential
value:
type: SMS_OTP
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
oauth:
summary: Add an OAuth credential
value:
type: OAUTH
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.-3_ETmSGOl4wGNLR1QSOMlHk5IvADpX3YdHFmTH9KmRu6sEhM20RsURjKrI4-_EKj7J_HtsdS1tCHm0iw2J0qtoczYFQqEW_U9qJD6QsuvTFx8Fj9rFa3ieYhZKi3kkBu6cADogUiudP50kf9345ATys2GrYm-ba5esgReW1WzGJG3SgCyIDnHFfxmeLjE2YE9EFxT73To3mPYAk0ywPL2MpFFV9F8I3PsnbDAxinaY75GeA8vJXATr8weEIXqHD2lxmXVE95qd2ZlcuyLUaEYyp9GXcOnx7SjhdJG88jl5BZQvxOVgBMo42iGjK674lSwsMiHpzLX98j6C786Rd9Q
passkey:
summary: Add a passkey credential
value:
type: PASSKEY
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
nickname: iPhone Face-ID
challenge: ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx
attestation:
credentialId: AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY
clientDataJson: eyJjaGFsbGVuZ2UiOiJBcktRaTJ5QVlIUGxnbkpORkJsbmVJd2NoUWRXWEJPVHJkQi1BbU1VQjIxTHgiLCJjbGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5kb250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0
attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow
transports:
- internal
- hybrid
responses:
'201':
description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthMethodResponse'
examples:
emailOtp:
summary: Email OTP credential created
value:
id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
type: EMAIL_OTP
nickname: example@lightspark.com
otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}'''
createdAt: '2026-04-08T15:30:01Z'
updatedAt: '2026-04-08T15:30:01Z'
smsOtp:
summary: SMS OTP credential created
value:
id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
type: SMS_OTP
nickname: '+14155550123'
otpEncryptionTargetBundle: '''{version:v1.0.0,data:7b227461726765745075626c6963...,dataSignature:30450221...,enclaveQuorumPublic:04a1b2c3...}'''
createdAt: '2026-04-08T15:30:01Z'
updatedAt: '2026-04-08T15:30:01Z'
oauth:
summary: OAuth credential created
value:
id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
type: OAUTH
nickname: example@lightspark.com
createdAt: '2026-04-08T15:30:01Z'
updatedAt: '2026-04-08T15:30:01Z'
passkey:
summary: Passkey credential created
value:
id: AuthMethod:019542f5-b3e7-1d02-0000-000000000001
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
type: PASSKEY
nickname: iPhone Face-ID
createdAt: '2026-04-08T15:30:01Z'
updatedAt: '2026-04-08T15:30:01Z'
'202':
description: Challenge issued. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account, then send that full stamp as `Grid-Wallet-Signature` and echo `requestId` as `Request-Id` on the retry.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthSignedRequestChallenge'
examples:
emailOtp:
summary: Additional email OTP credential challenge
value:
type: EMAIL_OTP
payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userEmail":"jane@example.com","userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_EMAIL"}'
requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
expiresAt: '2026-04-08T15:35:00Z'
smsOtp:
summary: Additional SMS OTP credential challenge
value:
type: SMS_OTP
payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"userId":"user_2m9F...","userPhoneNumber":"+14155550123"},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER"}'
requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
expiresAt: '2026-04-08T15:35:00Z'
oauth:
summary: Additional OAuth credential challenge
value:
type: OAUTH
payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"oauthProviders":[{"oidcToken":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9...","providerName":"Google"}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"}'
requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
expiresAt: '2026-04-08T15:35:00Z'
passkey:
summary: Additional passkey credential challenge
value:
type: PASSKEY
payloadToSign: '{"organizationId":"org_2m9F...","parameters":{"authenticators":[{"attestation":{"attestationObject":"o2NmbXRk...","clientDataJson":"eyJjaGFsbGVuZ2UiOiJBcktRa...","credentialId":"AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY"},"authenticatorName":"iPhone Face-ID","challenge":"ArkQi2yAYHPlgnJNFBlneIwchQdWXBOTrdB-AmMUB21Lx","transports":["internal","hybrid"]}],"userId":"user_2m9F..."},"timestampMs":"1775681700000","type":"ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"}'
requestId: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
expiresAt: '2026-04-08T15:35:00Z'
'400':
description: Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an email OTP credential while one already exists, `SMS_OTP_CREDENTIAL_ALREADY_EXISTS` when registering an SMS OTP credential while one already exists, `PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose WebAuthn credentialId is already attached to the internal account, or `INVALID_INPUT` when an OAuth `oidcToken` is malformed or has an unsupported issuer.
content:
application/json:
schema:
$ref: '#/components/schemas/Error400'
'401':
description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending challenge for an additional credential on the target internal account, when the `Request-Id` does not match an unexpired pending challenge, or when OAuth token authentication fails during credential registration.
content:
application/json:
schema:
$ref: '#/components/schemas/Error401'
'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'
get:
summary: List authentication credentials
description: 'Retrieve all authentication credentials registered on an Embedded Wallet internal account.
The response is not paginated: an internal account is expected to have a small, bounded number of credentials (typically 1–5), so all results are returned inline. Additional per-credential detail (such as active session expiry) is available on `GET /auth/sessions`.'
operationId: listAuthCredentials
tags:
- Embedded Wallet Auth
security:
- BasicAuth: []
parameters:
- name: accountId
in: query
description: Internal account id whose authentication credentials to list.
required: true
schema:
type: string
example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
responses:
'200':
description: Authentication credentials registered on the internal account. Returns an empty `data` array when the internal account has no credentials or when `accountId` does not match any internal account visible to the caller.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthCredentialListResponse'
examples:
multipleCredentials:
summary: Internal account with multiple authentication credentials
value:
data:
- 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'
- id: AuthMethod:019542f5-b3e7-1d02-0000-000000000004
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
type: OAUTH
nickname: example@lightspark.com
createdAt: '2026-04-08T15:35:00Z'
updatedAt: '2026-04-08T15:35:00Z'
- id: AuthMethod:019542f5-b3e7-1d02-0000-000000000003
accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002
type: PASSKEY
credentialId: KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew
nickname: iPhone Face-ID
createdAt: '2026-04-09T10:15:00Z'
updatedAt: '2026-04-09T10:15:00Z'
empty:
summary: No credentials registered on the account
value:
data: []
'400':
description: Bad request. Returned with `INVALID_INPUT` when the `accountId` query parameter is missing or not a valid `InternalAccount:<uuid>` identifier.
content:
application/json:
schema:
$ref: '#/components/schemas/Error400'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error401'
'500':
description: Internal service error
content:
application/json:
schema:
$ref: '#/components/schemas/Error500'
/auth/credentials/{id}:
delete:
summary: Revoke an authentication credential
description: 'Revoke an authentication credential on an Embedded Wallet internal account.
Revocation is a two-step flow because it must be authorized by a session on a *different* credential on the same internal account:
1. Call `DELETE /auth/credentials/{id}` with no headers. The response is `202` with a `payloadToSign`, `requestId`, and `expiresAt`.
2. Use the session API keypair of an existing verified credential on the same internal account — other than the one being revoked — to build an API-key stamp over `payloadToSign`, then retry the same `DELETE` 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 `204`.
The account must retain at least one authentication credential; an account with only a single credential cannot use this endpoint to revoke it.
'
operationId: revokeAuthCredential
tags:
- Embedded Wallet Auth
security:
- BasicAuth: []
parameters:
- name: id
in: path
description: The id of the authentication credential to revoke (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 session API keypair of an existing verified authentication credential on the same internal account (other than the one being revoked). Required on the signed retry; ignored on the initial call.
schema:
type: string
example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9
- name: Request-Id
in: header
required: false
description: The `requestId` returned in a prior `202` response, echoed back exactly on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`.
schema:
type: string
example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21
responses:
'202':
description: Challenge issued. 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 (other than the one being revoked), then echo `requestId` on the retry.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthSignedRequestChallenge'
'204':
description: Authentication credential revoked successfully.
'400':
description: Bad request. Also returned when the target internal account has only a single authentication credential, which cannot be revoked via this endpoint.
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 revocation challenge for this credential, or when the `Request-Id` does not match an unexpired pending challenge.
content:
application/json:
schema:
$ref: '#/components/schemas/Error401'
'404':
description: Authentication credential not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error404'
'500':
description: Internal service error
content:
application/json:
schema:
$ref: '#/components/schemas/Error500'
/auth/credentials/{id}/verify:
post:
summary: Verify an authentication credential
description: 'Complete the verification step for a previously created authentication credential and issue a session.
For `EMAIL_OTP` and `SMS_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 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 or SMS 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 OTP credentials, the response contains an `AuthSession`. For `OAUTH` and `PASSKEY` the session signing key is delivered as `encryptedSessionSigningKey` (HPKE-sealed to the supplied `clientPublicKey`); for OTP credentials 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
tags:
- Embedded Wallet Auth
security:
- BasicAuth: []
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` or `SMS_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` or `SMS_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"}'
smsOtp:
summary: Verify an SMS OTP credential (first leg)
value:
type: SMS_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 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` or `SMS_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 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'
/auth/credentials/{id}/challenge:
post:
summary: Re-issue an authentication credential challenge
description: 'Re-issue the challenge for an existing authentication credential.
For `EMAIL_OTP` and `SMS_OTP` credentials, this triggers a new one-time password to the contact on file and returns a fresh `otpEncryptionTargetBundle` for the cl
# --- truncated at 32 KB (115 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/lightspark/refs/heads/main/openapi/lightspark-embedded-wallet-auth-api-openapi.yml