Skip to content

add Embedded Wallet Auth endpoints for Email OTP create + verify#349

Open
DhruvPareek wants to merge 1 commit intomainfrom
04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp
Open

add Embedded Wallet Auth endpoints for Email OTP create + verify#349
DhruvPareek wants to merge 1 commit intomainfrom
04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp

Conversation

@DhruvPareek
Copy link
Copy Markdown

@DhruvPareek DhruvPareek commented Apr 17, 2026

Endpoints

  • POST /auth/credentials — register a credential on an internal account. 201 AuthMethod for the first credential; 202 AuthCredentialAdditionalChallengeOneOf when 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 AuthMethod

Create — additional credential (two-step with signed retry)

// first attempt
{ "type": "EMAIL_OTP", "accountId": "InternalAccount:…" }
→ 202 { "type": "EMAIL_OTP", "email", "payloadToSign", "requestId", "expiresAt" }

// retry — same body, add headers Grid-Wallet-Signature + Request-Id
→ 201 AuthMethod

Verify

{ "type": "EMAIL_OTP", "otp", "clientPublicKey" }
→ 200 AuthSession   // AuthMethod + { encryptedSessionSigningKey, expiresAt }

Resources

  • AuthMethodType — enum: OAUTH | EMAIL_OTP | PASSKEY
  • AuthMethod — credential: id, accountId, type, nickname, createdAt, updatedAt
  • AuthSessionAuthMethod + { encryptedSessionSigningKey, expiresAt }
  • AuthCredentialCreateRequest — shared create base: type, accountId
  • AuthCredentialVerifyRequest — shared verify base: type
  • AuthCredentialAdditionalChallenge — shared 202 base: type, payloadToSign, requestId, expiresAt
  • EmailOtpCredentialCreateRequestFields — EMAIL_OTP create fields: type
  • EmailOtpCredentialVerifyRequestFields — EMAIL_OTP verify fields: type, otp, clientPublicKey
  • EmailOtpCredentialAdditionalChallengeFields — EMAIL_OTP challenge fields: type, email
  • EmailOtpCredential{Create,Verify}Request / EmailOtpCredentialAdditionalChallengeallOf compositions of base + variant fields
  • AuthCredential{Create,Verify}RequestOneOf / AuthCredentialAdditionalChallengeOneOf — discriminated oneOf wrappers on type

Hierarchy (three-layer pattern)

AuthCredentialCreateRequestOneOf            ← oneOf, discriminated by type
└── EmailOtpCredentialCreateRequest         ← allOf
    ├── AuthCredentialCreateRequest         (shared base)
    └── EmailOtpCredentialCreateRequestFields   (variant fields)

AuthCredentialVerifyRequestOneOf            ← oneOf, discriminated by type
└── EmailOtpCredentialVerifyRequest         ← allOf
    ├── AuthCredentialVerifyRequest         (shared base)
    └── EmailOtpCredentialVerifyRequestFields   (variant fields)

AuthCredentialAdditionalChallengeOneOf      ← oneOf, discriminated by type
└── EmailOtpCredentialAdditionalChallenge   ← allOf
    ├── AuthCredentialAdditionalChallenge   (shared base)
    └── EmailOtpCredentialAdditionalChallengeFields   (variant fields)

AuthSession   ← allOf ← AuthMethod + { encryptedSessionSigningKey, expiresAt }

OAuth and Passkey variants land additively in each oneOf.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
grid-flow-builder Ready Ready Preview, Comment Apr 21, 2026 7:16pm

Request Review

Copy link
Copy Markdown
Author

DhruvPareek commented Apr 17, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 17, 2026

✱ Stainless preview builds

This PR will update the grid SDKs with the following commit messages.

kotlin

feat(api): add auth credentials create and verify endpoints

openapi

feat(api): add auth credentials create/verify endpoints and types

python

feat(api): add create/verify methods to auth.credentials

typescript

feat(api): add create/verify methods to auth credentials

Edit this comment to update them. They will appear in their respective SDK's changelogs.

grid-openapi studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅

grid-python studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

pip install https://pkg.stainless.com/s/grid-python/cffcec88c73dedb36e1042f2ce4499094fe6ab43/grid-0.0.1-py3-none-any.whl
grid-typescript studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

npm install https://pkg.stainless.com/s/grid-typescript/33e891f5b8bb4d454b8123b8d62256fab3684a8d/dist.tar.gz
grid-kotlin studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-04-21 19:20:06 UTC

@DhruvPareek DhruvPareek changed the title feat: add Embedded Wallet Auth endpoints for Email OTP add Embedded Wallet Auth endpoints for Email OTP create + verify Apr 17, 2026
@DhruvPareek DhruvPareek force-pushed the 04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp branch from f8abdb2 to 83f4592 Compare April 18, 2026 00:18
@DhruvPareek DhruvPareek force-pushed the 04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp branch from 83f4592 to 09412e0 Compare April 20, 2026 17:18
@DhruvPareek DhruvPareek force-pushed the 04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp branch from 09412e0 to 6ca7978 Compare April 20, 2026 22:29
@DhruvPareek DhruvPareek force-pushed the 04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp branch from 6ca7978 to 445daad Compare April 20, 2026 23:10
@DhruvPareek DhruvPareek force-pushed the 04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp branch from 445daad to c3c47fb Compare April 21, 2026 02:18
@DhruvPareek DhruvPareek force-pushed the 04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp branch from c3c47fb to f78dfa0 Compare April 21, 2026 06:37
@DhruvPareek DhruvPareek marked this pull request as ready for review April 21, 2026 17:39
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 21, 2026

Greptile Summary

This PR adds two new Embedded Wallet Auth endpoints — POST /auth/credentials (create/register a credential with a two-step 201/202 challenge flow) and POST /auth/credentials/{id}/verify (verify an EMAIL_OTP and issue an encrypted session signing key) — along with the full three-layer allOf/oneOf OpenAPI schema hierarchy to support future OAuth and Passkey variants.

  • The id path parameter on the verify endpoint is described as "the id of the internal account" but should be the credential (AuthMethod) id — mismatching the AuthMethod:… vs InternalAccount:… id formats will cause integrator errors.
  • The email field description in EmailOtpCredentialAdditionalChallengeFields incorrectly states the email was "supplied in the original unsigned request" and asks the client to "send the same email in the retry body" — but the create request body has no email field; the server derives it from the account record.

Confidence Score: 4/5

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

openapi/paths/auth/auth_credentials_{id}_verify.yaml (path param description) and openapi/components/schemas/auth/EmailOtpCredentialAdditionalChallengeFields.yaml (email field description).

Important Files Changed

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)
Loading

Fix All in Claude Code

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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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).

Suggested change
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.

Fix in Claude Code

Comment thread openapi/paths/auth/auth_credentials.yaml
type: EMAIL_OTP
otp: '123456'
clientPublicKey: |
-----BEGIN PUBLIC KEY-----
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure this is the right format @carsonp6 can you provide an example?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait why?

@@ -0,0 +1,9 @@
type: object
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is the three-layer discriminator pattern that openapi/README.md asks for (lines 344–415)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches existing precedent like IndividualCustomerFields.yaml / BusinessCustomerFields.yamlare also each referenced exactly once (by IndividualCustomer.yaml / BusinessCustomer.yaml) for the same reason.

description: Authentication credential created successfully
content:
application/json:
schema:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not certain if you want creation to return an AuthMethod resource but verify returning a session resource.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless they always need to call verify after resource creation? which would be ok I think?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since changed this flow to always need to call verify after resource creation, i think this is okay?

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
@DhruvPareek DhruvPareek force-pushed the 04-17-feat_add_embedded_wallet_auth_endpoints_for_email_otp branch from f78dfa0 to 10ae784 Compare April 21, 2026 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants