add Embedded Wallet Auth endpoints for Email OTP create + verify#349
add Embedded Wallet Auth endpoints for Email OTP create + verify#349DhruvPareek wants to merge 1 commit intomainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
✱ Stainless preview buildsThis PR will update the kotlin openapi python typescript Edit this comment to update them. They will appear in their respective SDK's changelogs. ✅ grid-openapi studio · code · diff
✅ grid-python studio · code · diff
✅ grid-typescript studio · code · diff
✅ grid-kotlin studio · code · diff
This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push. |
f8abdb2 to
83f4592
Compare
83f4592 to
09412e0
Compare
09412e0 to
6ca7978
Compare
6ca7978 to
445daad
Compare
445daad to
c3c47fb
Compare
c3c47fb to
f78dfa0
Compare
Greptile SummaryThis PR adds two new Embedded Wallet Auth endpoints —
Confidence Score: 4/5Two P1 documentation errors need correction before merge to avoid integrator confusion. The schema structure and flow design are solid, but two description-level defects could cause real integration failures: the wrong id type in the verify path parameter (account vs credential) and the incorrect "email in retry body" instruction when no such field exists in the request schema.
|
| Filename | Overview |
|---|---|
| openapi/paths/auth/auth_credentials_{id}_verify.yaml | Verify endpoint — id path param is described as an internal account id but should be a credential (AuthMethod) id per REST conventions and schema definitions. |
| openapi/components/schemas/auth/EmailOtpCredentialAdditionalChallengeFields.yaml | email field description incorrectly claims the email was supplied by the client; it is actually server-derived from the account record, and the create request body has no email field. |
| openapi/paths/auth/auth_credentials.yaml | Create credential endpoint — well-structured two-step flow with clear 201/202 semantics; missing 404 for unknown accountId. |
| openapi/components/schemas/auth/AuthCredentialCreateRequestOneOf.yaml | Discriminated oneOf create request wrapper — currently EMAIL_OTP only; structure correctly anticipates future OAuth/Passkey variants. |
| openapi/components/schemas/auth/AuthCredentialVerifyRequestOneOf.yaml | Discriminated oneOf verify request wrapper — mirrors create pattern consistently. |
| openapi/components/schemas/auth/AuthSession.yaml | AuthSession extends AuthMethod with encryptedSessionSigningKey and expiresAt — clean allOf composition, all required fields present. |
| openapi/components/schemas/auth/AuthMethod.yaml | AuthMethod schema — all required fields defined, types and examples are consistent. |
| openapi/components/schemas/auth/EmailOtpCredentialVerifyRequestFields.yaml | Verify fields schema — otp, clientPublicKey, and narrowed type discriminator are all present and documented correctly. |
| openapi/components/schemas/auth/AuthMethodType.yaml | AuthMethodType enum — OAUTH, EMAIL_OTP, PASSKEY values with clear descriptions. |
Sequence Diagram
sequenceDiagram
participant C as Client
participant G as Grid API
Note over C,G: First credential on account
C->>G: POST /auth/credentials {type:EMAIL_OTP, accountId}
G-->>C: 201 AuthMethod (OTP email sent)
C->>G: POST /auth/credentials/{id}/verify {type:EMAIL_OTP, otp, clientPublicKey}
G-->>C: 200 AuthSession (encryptedSessionSigningKey, expiresAt)
Note over C,G: Additional credential (two-step)
C->>G: POST /auth/credentials {type:EMAIL_OTP, accountId}
G-->>C: 202 {payloadToSign, requestId, expiresAt, email}
Note over C: Sign payloadToSign with existing session key
C->>G: POST /auth/credentials + Grid-Wallet-Signature + Request-Id headers
G-->>C: 201 AuthMethod (OTP email sent on signed retry)
C->>G: POST /auth/credentials/{id}/verify {type:EMAIL_OTP, otp, clientPublicKey}
G-->>C: 200 AuthSession (encryptedSessionSigningKey, expiresAt)
Prompt To Fix All With AI
This is a comment left during a code review.
Path: openapi/paths/auth/auth_credentials_{id}_verify.yaml
Line: 22
Comment:
**Misleading path parameter description — credential id vs. account id**
The description says "the id of the internal account," but the path `/auth/credentials/{id}/verify` follows REST conventions where `{id}` refers to the specific credential (`AuthMethod`) being verified. The `AuthMethod` schema returns a credential `id` (`AuthMethod:…`) separate from `accountId` (`InternalAccount:…`). Describing it as an account id will confuse integrators who pass an `InternalAccount:…` id when the server expects an `AuthMethod:…` id (or vice versa).
```suggestion
description: The id of the authentication credential (AuthMethod) to verify.
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: openapi/components/schemas/auth/EmailOtpCredentialAdditionalChallengeFields.yaml
Line: 13-20
Comment:
**Incorrect `email` field description — email is server-derived, not client-supplied**
The description says "the email address supplied in the original unsigned request" and "the signed retry must send the same email in its request body," but `EmailOtpCredentialCreateRequestFields` contains only `type` — no `email` field. The server derives the email from the customer record tied to `accountId`. Telling consumers to "send the same email in the retry body" is factually wrong and will mislead integrators.
```suggestion
email:
type: string
format: email
description: >-
The email address on the customer record tied to the internal account.
This is the address to which the OTP will be sent after the signed retry
is accepted.
example: example@lightspark.com
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: openapi/paths/auth/auth_credentials.yaml
Line: 107-112
Comment:
**Missing 404 response for unknown `accountId`**
If the supplied `accountId` refers to a non-existent internal account the server will likely return `404 Not Found`, but this response code is not documented. Consumers will have no way to distinguish "malformed request" (400) from "account not found" (404) without this entry.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: add Embedded Wallet Auth endpoints..." | Re-trigger Greptile
| parameters: | ||
| - name: id | ||
| in: path | ||
| description: The id of the internal account whose authentication credential is being verified. |
There was a problem hiding this comment.
Misleading path parameter description — credential id vs. account id
The description says "the id of the internal account," but the path /auth/credentials/{id}/verify follows REST conventions where {id} refers to the specific credential (AuthMethod) being verified. The AuthMethod schema returns a credential id (AuthMethod:…) separate from accountId (InternalAccount:…). Describing it as an account id will confuse integrators who pass an InternalAccount:… id when the server expects an AuthMethod:… id (or vice versa).
| description: The id of the internal account whose authentication credential is being verified. | |
| description: The id of the authentication credential (AuthMethod) to verify. |
Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/paths/auth/auth_credentials_{id}_verify.yaml
Line: 22
Comment:
**Misleading path parameter description — credential id vs. account id**
The description says "the id of the internal account," but the path `/auth/credentials/{id}/verify` follows REST conventions where `{id}` refers to the specific credential (`AuthMethod`) being verified. The `AuthMethod` schema returns a credential `id` (`AuthMethod:…`) separate from `accountId` (`InternalAccount:…`). Describing it as an account id will confuse integrators who pass an `InternalAccount:…` id when the server expects an `AuthMethod:…` id (or vice versa).
```suggestion
description: The id of the authentication credential (AuthMethod) to verify.
```
How can I resolve this? If you propose a fix, please make it concise.| type: EMAIL_OTP | ||
| otp: '123456' | ||
| clientPublicKey: | | ||
| -----BEGIN PUBLIC KEY----- |
There was a problem hiding this comment.
i'm not sure this is the right format @carsonp6 can you provide an example?
There was a problem hiding this comment.
also i'm not sure the verification end point needs to pass in the clientPublicKey again. I may have accidentally added that when working with the assumption that keys for sessions could be generated on the client side
There was a problem hiding this comment.
Carson said that the clientPublicKey should be passed into any request where the response has a session key, so we removed the clientPublicKey from the request for POST /auth/credentials when auth method is OTP, and added it to the request for POST /auth/credentials/{id}/verify
| @@ -0,0 +1,9 @@ | |||
| type: object | |||
There was a problem hiding this comment.
i don't know if this needs to be in a separate schema file. It's only referenced once, so it feels like it could be part of the EmailOtpCredentialCreateRequest
| description: Authentication credential created successfully | ||
| content: | ||
| application/json: | ||
| schema: |
There was a problem hiding this comment.
I'm not certain if you want creation to return an AuthMethod resource but verify returning a session resource.
There was a problem hiding this comment.
unless they always need to call verify after resource creation? which would be ok I think?
New `Embedded Wallet Auth` endpoints for registering and verifying end-user authentication credentials. EMAIL_OTP is wired today; OAuth and Passkey will land as additional branches in the discriminated `oneOf`.
**Resources defined**
- `AuthMethodType` — enum: `OAUTH`, `EMAIL_OTP`, `PASSKEY`
- `AuthMethod` — base credential response (id, accountId, type, nickname, timestamps)
- `AuthSession` — verified credential (`AuthMethod` via `allOf` + `encryptedSessionSigningKey` + `expiresAt`)
- `CreateAuthCredentialRequest` / `VerifyAuthCredentialRequest` — discriminated `oneOf` envelopes keyed on `type`, EMAIL_OTP branch only today
- `EmailOtpCredentialCreateRequest` / `EmailOtpCredentialVerifyRequest` — EMAIL_OTP branch schemas
**Endpoints defined**
- `POST /auth/credentials` — registers a credential and triggers the OTP email; returns an unverified `AuthMethod` (201)
- `POST /auth/credentials/{id}/verify` — exchanges the OTP for a session; returns an `AuthSession` (200). `{id}` is the internal account id
**Request shapes**
- Create: `{ type: "EMAIL_OTP", email, clientPublicKey }`
- Verify: `{ type: "EMAIL_OTP", otp }`
**Response shapes**
- Create → `AuthMethod` (no session yet)
- Verify → `AuthSession` (adds `encryptedSessionSigningKey`, `expiresAt`)
**Implementation notes**
- Request bodies modeled as discriminated `oneOf` on `type` from day one so OAuth/Passkey arrive as additive branch files — no refactor of the EMAIL_OTP code paths
- Split `AuthMethod` (unverified) and `AuthSession` (verified, `allOf` extension) rather than a single `AuthMethod` with optional `encryptedSessionSigningKey` / `expiresAt` — the "verify always issues a session" contract is enforced by the schema rather than runtime checks on optional fields
- Security: `BasicAuth` on both — platform calls or forwards on behalf of the user
- New `Embedded Wallet Auth` tag added to `openapi/openapi.yaml`; bundled `openapi.yaml` and `mintlify/openapi.yaml` regenerated via `make build`
**Next in stack**
- `POST /auth/credentials/{id}/challenge` (resend OTP) — follow-up PR
f78dfa0 to
10ae784
Compare

Endpoints
POST /auth/credentials— register a credential on an internal account.201 AuthMethodfor the first credential;202 AuthCredentialAdditionalChallengeOneOfwhen one already exists (signed retry required).POST /auth/credentials/{id}/verify— verify an OTP and receive an encrypted session signing key.EMAIL_OTP request / response
Create — first credential
{ "type": "EMAIL_OTP", "accountId": "InternalAccount:…" } → 201 AuthMethodCreate — additional credential (two-step with signed retry)
Verify
{ "type": "EMAIL_OTP", "otp", "clientPublicKey" } → 200 AuthSession // AuthMethod + { encryptedSessionSigningKey, expiresAt }Resources
AuthMethodType— enum:OAUTH | EMAIL_OTP | PASSKEYAuthMethod— credential:id, accountId, type, nickname, createdAt, updatedAtAuthSession—AuthMethod + { encryptedSessionSigningKey, expiresAt }AuthCredentialCreateRequest— shared create base:type, accountIdAuthCredentialVerifyRequest— shared verify base:typeAuthCredentialAdditionalChallenge— shared 202 base:type, payloadToSign, requestId, expiresAtEmailOtpCredentialCreateRequestFields— EMAIL_OTP create fields:typeEmailOtpCredentialVerifyRequestFields— EMAIL_OTP verify fields:type, otp, clientPublicKeyEmailOtpCredentialAdditionalChallengeFields— EMAIL_OTP challenge fields:type, emailEmailOtpCredential{Create,Verify}Request/EmailOtpCredentialAdditionalChallenge—allOfcompositions of base + variant fieldsAuthCredential{Create,Verify}RequestOneOf/AuthCredentialAdditionalChallengeOneOf— discriminatedoneOfwrappers ontypeHierarchy (three-layer pattern)
OAuth and Passkey variants land additively in each
oneOf.