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
2 changes: 2 additions & 0 deletions mintlify/snippets/cards/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ for the full table. When activation completes, a
"InternalAccount:019542f5-b3e7-1d02-0000-000000000002"
],
"currency": "USD",
"processorRef": "card_b81c2a4f",
"issuerRef": "lead_card_7a1b9c3d",
"createdAt": "2026-05-08T14:10:00Z",
"updatedAt": "2026-05-08T14:11:00Z"
}
Expand Down
2 changes: 2 additions & 0 deletions mintlify/snippets/cards/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ activation after issuance:
"InternalAccount:019542f5-b3e7-1d02-0000-000000000002"
],
"currency": "USD",
"processorRef": "card_b81c2a4f",
"issuerRef": "lead_card_7a1b9c3d",
"createdAt": "2026-05-08T14:10:00Z",
"updatedAt": "2026-05-08T14:11:00Z"
}
Expand Down
5 changes: 3 additions & 2 deletions mintlify/snippets/global-accounts/authentication.mdx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
Every Global Account action beyond receiving funds must be authorized by a session signing key. Sessions are issued by verifying one of three **credential types** on the account:
Every Global Account action beyond receiving funds must be authorized by a session signing key. Sessions are issued by verifying one of four **credential types** on the account:

| Type | When to use it |
|---|---|
| **`PASSKEY`** | Best default. Biometric, phishing-resistant, usable across the user's devices via iCloud Keychain / Google Password Manager. |
| **`OAUTH`** | Your platform already authenticates the user via OIDC (Google, Apple, your own IdP) and you want Grid to trust the same identity. |
| **`EMAIL_OTP`** | Lowest-friction option. Works on any device with email access — no biometric hardware, identity provider, or client SDK required beyond the code entry field. |
| **`SMS_OTP`** | Same flow as `EMAIL_OTP`, but delivered to the user's phone number instead of email. Useful when phone is the primary contact channel. |

A single internal account can hold one `EMAIL_OTP` credential and multiple distinct `PASSKEY` credentials concurrently. `OAUTH` credentials can be added for each supported provider identity.
A single internal account can hold one `EMAIL_OTP` credential, one `SMS_OTP` credential, and multiple distinct `PASSKEY` credentials concurrently. `OAUTH` credentials can be added for each supported provider identity.

## Registration vs. verification

Expand Down
10 changes: 5 additions & 5 deletions mintlify/snippets/sandbox-global-account-magic.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
The Grid sandbox lets you exercise Global Account auth flows without moving real money. Email OTP uses the fixed sandbox code `000000` — HPKE-encrypt that code in the `encryptedOtpBundle` just like production. Passkey auth can use the same browser WebAuthn ceremony as production, and signed wallet actions can use the same session signing key and `Grid-Wallet-Signature` stamp as production. OAuth uses JWT-shaped sandbox OIDC tokens: sandbox skips real IdP signature verification, but still validates token claims, freshness, credential identity, and verify-time nonce binding.
The Grid sandbox lets you exercise Global Account auth flows without moving real money. Email OTP and SMS OTP use the fixed sandbox code `000000` — HPKE-encrypt that code in the `encryptedOtpBundle` just like production. Passkey auth can use the same browser WebAuthn ceremony as production, and signed wallet actions can use the same session signing key and `Grid-Wallet-Signature` stamp as production. OAuth uses JWT-shaped sandbox OIDC tokens: sandbox skips real IdP signature verification, but still validates token claims, freshness, credential identity, and verify-time nonce binding.

Sandbox runs real HPKE end-to-end for EMAIL_OTP: 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 the user "receives" instead of a real email delivery.
Sandbox runs real HPKE end-to-end for EMAIL_OTP and SMS_OTP: 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 the user "receives" instead of a real email or SMS delivery.

Authentication failures return `401 UNAUTHORIZED` with a `reason` field that names the specific check that failed. A malformed OIDC JWT can return `400 INVALID_INPUT` before authentication starts.

### Email OTP code
### Email and SMS OTP code

HPKE-encrypt the code `000000` (together with your TEK public key) inside `encryptedOtpBundle`. The sandbox skips email delivery but runs real HPKE decryption and signature verification.
HPKE-encrypt the code `000000` (together with your TEK public key) inside `encryptedOtpBundle`. The sandbox skips email and SMS delivery but runs real HPKE decryption and signature verification.

See <a href="/global-accounts/integration-guides/client-keys#encrypt-the-otp-code-email_otp-only">Encrypt the OTP code</a> for how to build the bundle. The flow is:
See <a href="/global-accounts/integration-guides/client-keys#encrypt-the-otp-code-email_otp-only">Encrypt the OTP code</a> for how to build the bundle. The flow is the same for both `EMAIL_OTP` and `SMS_OTP`:

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.

P2 Stale anchor points to an EMAIL_OTP-only section heading

The link #encrypt-the-otp-code-email_otp-only still resolves to a section in client-keys.mdx titled ## Encrypt the OTP code (EMAIL_OTP only). That heading wasn't updated in this PR, so visitors who follow the link will land on a section that explicitly says "EMAIL_OTP only" — directly contradicting the updated text here that says the flow is the same for both credential types. The client-keys.mdx heading should be updated to drop the "(EMAIL_OTP only)" qualifier, and the anchor in this file updated accordingly.

Context Used: mintlify/AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: mintlify/snippets/sandbox-global-account-magic.mdx
Line: 11

Comment:
**Stale anchor points to an `EMAIL_OTP`-only section heading**

The link `#encrypt-the-otp-code-email_otp-only` still resolves to a section in `client-keys.mdx` titled `## Encrypt the OTP code (EMAIL_OTP only)`. That heading wasn't updated in this PR, so visitors who follow the link will land on a section that explicitly says "EMAIL_OTP only" — directly contradicting the updated text here that says the flow is the same for both credential types. The `client-keys.mdx` heading should be updated to drop the "(EMAIL_OTP only)" qualifier, and the anchor in this file updated accordingly.

**Context Used:** mintlify/AGENTS.md ([source](https://app.greptile.com/lightspark/github/lightsparkdev/grid-api/-/custom-context?memory=51934046-75fb-42d3-9870-f42d61cb60e3))

How can I resolve this? If you propose a fix, please make it concise.


1. Call `POST /auth/credentials/{id}/challenge` to get `otpEncryptionTargetBundle`
2. Generate a TEK key pair and HPKE-encrypt `{otp_code: "000000", public_key: tekPublicKeyHex}`
Expand Down
Loading