Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 101 additions & 26 deletions mintlify/openapi.yaml

Large diffs are not rendered by default.

127 changes: 101 additions & 26 deletions openapi.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ 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 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`.
challenge is computed from. Ignored for `EMAIL_OTP` and `SMS_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:
Expand All @@ -21,5 +21,6 @@ properties:
total). The matching private key must remain on the client. Grid
bakes this key into the 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`.
signing key is sealed to the client. Ignored for `EMAIL_OTP` and
`SMS_OTP`.
example: 04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
oneOf:
- $ref: ./EmailOtpCredentialCreateRequest.yaml
- $ref: ./SmsOtpCredentialCreateRequest.yaml
- $ref: ./OauthCredentialCreateRequest.yaml
- $ref: ./PasskeyCredentialCreateRequest.yaml
discriminator:
propertyName: type
mapping:
EMAIL_OTP: ./EmailOtpCredentialCreateRequest.yaml
SMS_OTP: ./SmsOtpCredentialCreateRequest.yaml
OAUTH: ./OauthCredentialCreateRequest.yaml
PASSKEY: ./PasskeyCredentialCreateRequest.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
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
`POST /auth/credentials/{id}/challenge`. For `EMAIL_OTP` and `SMS_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.
`AuthMethodResponse` shape directly for all credential types.
oneOf:
- $ref: ./AuthMethodResponse.yaml
- $ref: ./PasskeyAuthChallenge.yaml
discriminator:
propertyName: type
mapping:
EMAIL_OTP: ./AuthMethodResponse.yaml
SMS_OTP: ./AuthMethodResponse.yaml
PASSKEY: ./PasskeyAuthChallenge.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
oneOf:
- $ref: ./EmailOtpCredentialVerifyRequest.yaml
- $ref: ./SmsOtpCredentialVerifyRequest.yaml
- $ref: ./OauthCredentialVerifyRequest.yaml
- $ref: ./PasskeyCredentialVerifyRequest.yaml
discriminator:
propertyName: type
mapping:
EMAIL_OTP: ./EmailOtpCredentialVerifyRequest.yaml
SMS_OTP: ./SmsOtpCredentialVerifyRequest.yaml
OAUTH: ./OauthCredentialVerifyRequest.yaml
PASSKEY: ./PasskeyCredentialVerifyRequest.yaml
7 changes: 4 additions & 3 deletions openapi/components/schemas/auth/AuthMethod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ properties:
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.
this is the email address; for SMS_OTP credentials this is the E.164
phone number; for OAUTH credentials it is typically the email claim from
the OIDC token; for PASSKEY credentials it is the validated nickname
provided at registration time.
example: example@lightspark.com
createdAt:
type: string
Expand Down
16 changes: 8 additions & 8 deletions openapi/components/schemas/auth/AuthMethodResponse.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
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
response on `POST /auth/credentials` and inside
`AuthCredentialResponseOneOf` for the `EMAIL_OTP` / `SMS_OTP` branches of
`POST /auth/credentials/{id}/challenge`. The only difference from
`AuthMethod` is `unevaluatedProperties: false`, which disambiguates the
oneOf against `PasskeyAuthChallenge` — without the strictness, an
`AuthMethod` with extra fields would ambiguously match both branches.


For `EMAIL_OTP` credentials, responses that initiate or reissue an OTP
challenge carry `otpEncryptionTargetBundle` so the client can
For `EMAIL_OTP` and `SMS_OTP` credentials, responses that initiate or
reissue an OTP challenge carry `otpEncryptionTargetBundle` so the client can
HPKE-encrypt the OTP code in the subsequent
`POST /auth/credentials/{id}/verify` call without the plaintext code ever
transiting the server. First-time EMAIL_OTP wallet bootstrap registration
Expand All @@ -23,10 +23,10 @@ allOf:
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
challenge. Returned only on `EMAIL_OTP` and `SMS_OTP` responses
that initiate or reissue an OTP challenge, such as
`POST /auth/credentials/{id}/challenge` and signed-retry add
responses. It is omitted from first-time EMAIL_OTP
wallet bootstrap registration; call
`POST /auth/credentials/{id}/challenge` for the new credential if
it is absent. The client generates an ephemeral P-256 keypair
Expand Down
3 changes: 3 additions & 0 deletions openapi/components/schemas/auth/AuthMethodType.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ type: string
enum:
- OAUTH
- EMAIL_OTP
- SMS_OTP
- PASSKEY
description: >-
The type of authentication credential.
Expand All @@ -11,4 +12,6 @@ description: >-

- `EMAIL_OTP`: A one-time password delivered to the user's email address.

- `SMS_OTP`: A one-time password delivered to the user's phone number.

- `PASSKEY`: A WebAuthn passkey bound to the user's device.
4 changes: 2 additions & 2 deletions openapi/components/schemas/auth/AuthSession.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ allOf:


Returned only by session-issuing responses for `OAUTH` and
`PASSKEY` credentials. `EMAIL_OTP` sessions omit this field —
the client generates a TEK keypair before verification and
`PASSKEY` credentials. `EMAIL_OTP` and `SMS_OTP` sessions omit this
field — the client generates a TEK keypair before verification and
retains the private key throughout, so the server has nothing
to deliver. Always omitted from list responses
(`GET /auth/sessions`) since Grid does not retain the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ description: >-
202 response returned from Embedded Wallet Auth endpoints that require
a signed retry — `POST /auth/credentials` (adding an additional
credential), `DELETE /auth/credentials/{id}` (revoking a credential),
`DELETE /auth/sessions/{id}` (revoking a session), and the `EMAIL_OTP`
branch of `POST /auth/credentials/{id}/verify` (the secure OTP login
`DELETE /auth/sessions/{id}` (revoking a session), and the `EMAIL_OTP` /
`SMS_OTP` branch of `POST /auth/credentials/{id}/verify` (the secure OTP login
flow, where the client submits an `encryptedOtpBundle` and receives a
`verificationToken` to sign for the second-leg session issuance).
Carries the signing fields from `SignedRequestChallenge` plus the
Expand All @@ -18,7 +18,7 @@ description: >-
The keypair used to compute the stamp depends on the operation. For
credential / session management retries, sign with the session API
keypair of an existing verified credential on the same internal
account. For the `EMAIL_OTP` verify retry, sign with the ephemeral
account. For OTP verify retries, sign with the ephemeral
Target Encryption Key (TEK) the client generated for this login —
its public key is the one carried inside the `encryptedOtpBundle`
and bound into the `verificationToken`, and it becomes the client's
Expand All @@ -35,6 +35,6 @@ allOf:
Credential type relevant to this challenge: the credential type
being added (`POST /auth/credentials`), revoked
(`DELETE /auth/credentials/{id}`), or authenticated
(`EMAIL_OTP` branch of `POST /auth/credentials/{id}/verify`).
(`EMAIL_OTP` / `SMS_OTP` branch of `POST /auth/credentials/{id}/verify`).
For session revocation, this is the type of credential that
issued the session (`DELETE /auth/sessions/{id}`).
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: SMS OTP Credential Create Request
allOf:
- $ref: ./AuthCredentialCreateRequest.yaml
- $ref: ./SmsOtpCredentialCreateRequestFields.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type: object
required:
- type
properties:
type:
type: string
enum:
- SMS_OTP
description: >-
Discriminator value identifying this as an SMS OTP credential.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: SMS OTP Credential Verify Request
allOf:
- $ref: ./AuthCredentialVerifyRequest.yaml
- $ref: ./SmsOtpCredentialVerifyRequestFields.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type: object
required:
- type
- encryptedOtpBundle
description: >-
Verify an SMS-OTP credential via the same secure two-leg flow as email OTP.
The client HPKE-encrypts the OTP code (together with its public key) under
the `otpEncryptionTargetBundle` returned from registration or
`POST /auth/credentials/{id}/challenge`, submits the result here, and
receives `202` with a `payloadToSign` carrying a `verificationToken` bound
to the client's public key. The client signs that token with the matching
private key and retries this request with `Grid-Wallet-Signature` +
`Request-Id` headers to obtain the session. Plaintext OTP codes are never
sent over the wire.
properties:
type:
type: string
enum:
- SMS_OTP
description: Discriminator value identifying this as an SMS OTP verification.
encryptedOtpBundle:
type: string
description: >-
HPKE-sealed OTP attempt. Same format and retry semantics as
`EmailOtpCredentialVerifyRequest.encryptedOtpBundle`.
example: '{"encappedPublic":"044f631a2d890bc6668d997ee184e190650d06adf970987568ec641214a00403b73effe1ef406c60a5cde8508a4484567ddb8056fbd493bee614cd727aef02a838","ciphertext":"1fa1023390a56539aa48cbb380aa28f544ed5cc04861566bb806e25ba026f14660eaf4140a05b388dd012eaa899759a6a92576cdca8c1b7d12e147bd96cc26ed9f74886794155d8ac5cf0fdc"}'
2 changes: 2 additions & 0 deletions openapi/components/schemas/errors/Error400.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ properties:
| 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 |
| SMS_OTP_CREDENTIAL_ALREADY_EXISTS | An SMS_OTP credential is already registered on the target internal account; only one SMS OTP credential is supported per internal account at this time |
| PASSKEY_CREDENTIAL_ALREADY_EXISTS | A PASSKEY credential with the same WebAuthn credentialId is already registered on the target internal account |
| STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable |
| STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked |
Expand Down Expand Up @@ -87,6 +88,7 @@ properties:
- UNSUITABLE_DOCUMENT
- INCOMPLETE
- EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS
- SMS_OTP_CREDENTIAL_ALREADY_EXISTS
- PASSKEY_CREDENTIAL_ALREADY_EXISTS
- STABLECOIN_PROVIDER_ACCOUNT_INVALID
- STABLECOIN_PROVIDER_ACCOUNT_REVOKED
Expand Down
2 changes: 1 addition & 1 deletion openapi/openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 34 additions & 12 deletions openapi/paths/auth/auth_credentials.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ post:

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 (`OAUTH` or `PASSKEY`), or to add `EMAIL_OTP`
back after it has been removed. Only one `EMAIL_OTP` credential is
supported per internal account; multiple distinct `PASSKEY` credentials
may be registered.
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
Expand All @@ -21,8 +21,8 @@ post:
`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
`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:
Expand Down Expand Up @@ -64,6 +64,11 @@ post:
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:
Expand All @@ -88,10 +93,9 @@ post:
'201':
description: >-
Authentication credential created successfully. The body is the
created `AuthMethod` for all three credential types. For `EMAIL_OTP`,
the email is the customer email tied to the internal account. When
the response is for adding EMAIL_OTP back to an existing wallet
through the signed-retry flow, it also carries
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
Expand All @@ -118,6 +122,16 @@ post:
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:
Expand Down Expand Up @@ -155,6 +169,13 @@ post:
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:
Expand All @@ -172,8 +193,9 @@ post:
'400':
description: >-
Bad request. Returned with `EMAIL_OTP_CREDENTIAL_ALREADY_EXISTS`
when registering an email OTP credential while one already exists, or
`PASSKEY_CREDENTIAL_ALREADY_EXISTS` when registering a passkey whose
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.
Expand Down
13 changes: 8 additions & 5 deletions openapi/paths/auth/auth_credentials_{id}_challenge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ post:
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
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 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
Expand Down Expand Up @@ -51,7 +51,7 @@ post:
requestBody:
description: >-
Request body. Required when re-challenging a `PASSKEY` credential
(must carry `clientPublicKey`). Ignored for `EMAIL_OTP`,
(must carry `clientPublicKey`). Ignored for `EMAIL_OTP` and `SMS_OTP`,
where the credential type alone is sufficient — the OTP is delivered
out-of-band. OAuth credentials do not use this endpoint.
required: false
Expand All @@ -67,12 +67,15 @@ post:
emailOtp:
summary: Re-challenge an email-OTP credential (empty body)
value: {}
smsOtp:
summary: Re-challenge an SMS-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`
`EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a
new OTP 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`.
Expand Down
Loading
Loading