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
3 changes: 2 additions & 1 deletion .github/workflows/stainless-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
org: ${{ env.STAINLESS_ORG }}
project: ${{ env.STAINLESS_PROJECT }}
oas_path: ${{ env.OAS_PATH }}
guess_config: true
config_path: .stainless/stainless.yml

merge:
if: github.event.action == 'closed' && github.event.pull_request.merged == true
Expand All @@ -58,3 +58,4 @@ jobs:
org: ${{ env.STAINLESS_ORG }}
project: ${{ env.STAINLESS_PROJECT }}
oas_path: ${{ env.OAS_PATH }}
config_path: .stainless/stainless.yml
1 change: 1 addition & 0 deletions mintlify/openapi.yaml

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

40 changes: 30 additions & 10 deletions mintlify/snippets/sandbox-global-account-magic.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The Grid sandbox accepts a small set of magic values for Global Account flows, so you can exercise the full request shape without standing up Turnkey, WebAuthn, or an OIDC provider. OTP, passkey, and wallet signatures use fixed sandbox-only values. OAuth uses JWT-shaped sandbox OIDC tokens: sandbox skips real IdP signature verification, but still validates the 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 uses the fixed sandbox code `000000`. Passkey auth can use the same browser WebAuthn ceremony as production. Wallet signatures use fixed sandbox-only values. 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.

A wrong magic value or sandbox OIDC authentication failure returns `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.
Sandbox-only compatibility values are still available for some flows, but they do not exercise the production-shaped client implementation. 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

Expand All @@ -20,11 +20,29 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMet

Any other code returns `401 UNAUTHORIZED` with `reason: "Invalid OTP code"`.

### Passkey assertion signature

Pass `sandbox-valid-passkey-signature` as `assertion.signature` on `POST /auth/credentials/{id}/verify` when the credential type is `PASSKEY`. The sandbox accepts the rest of the assertion as-is and skips the WebAuthn signature check.

Passkey reauthentication is a two-step `/challenge` → `/verify` flow. The `clientPublicKey` is sent on `/challenge` (so Grid can seal the session signing key to your device) — the magic value bypasses the credential check, not the HPKE plumbing, so the public key is still required.
### Passkey WebAuthn ceremony

For new sandbox integrations, use the same WebAuthn calls you plan to use in production.

<Steps>
<Step title="Create a WebAuthn credential">
Generate your own WebAuthn registration challenge and call `navigator.credentials.create()`.
</Step>
<Step title="Register the passkey">
Register the passkey with `POST /auth/credentials`, passing the challenge and attestation returned by the browser.
</Step>
<Step title="Request a challenge">
Reauthenticate with `POST /auth/credentials/{id}/challenge`, passing the P-256 `clientPublicKey` that Grid should seal the session signing key to.
</Step>
<Step title="Run the browser assertion">
Pass the returned `challenge` into `navigator.credentials.get()` using the returned `credentialId` in `allowCredentials`.
</Step>
<Step title="Verify the assertion">
Verify with `POST /auth/credentials/{id}/verify`, passing the browser assertion and echoing `Request-Id` from the challenge response.
</Step>
</Steps>

The sandbox validates the registered credential ID, WebAuthn challenge, origin/RP binding, user-presence bit, assertion signature, and signature counter. A successful verify response includes `encryptedSessionSigningKey`, sealed to the `clientPublicKey`, just like production.

```bash
# 1. /challenge with clientPublicKey
Expand All @@ -35,7 +53,7 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMet
"clientPublicKey": "04f45f2a..."
}'

# 2. /verify with the magic signature, no clientPublicKey
# 2. /verify with the browser assertion returned by navigator.credentials.get()
curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMethod:abc123/verify \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
Expand All @@ -46,12 +64,14 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMet
"credentialId": "...",
"clientDataJson": "...",
"authenticatorData": "...",
"signature": "sandbox-valid-passkey-signature"
"signature": "..."
}
}'
```

Any other signature returns `401 UNAUTHORIZED` with `reason: "Invalid passkey signature"`.
<Note>
The legacy sandbox-only assertion signature `sandbox-valid-passkey-signature` is still accepted for compatibility, but it skips WebAuthn verification and should not be used for production-shaped sandbox tests.
</Note>

### OAuth (OIDC) token

Expand Down
1 change: 1 addition & 0 deletions openapi.yaml

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

5 changes: 5 additions & 0 deletions openapi/components/schemas/auth/PasskeyAssertion.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
title: Passkey Assertion
type: object
description: >-
WebAuthn assertion returned by `navigator.credentials.get()`. In sandbox,
Grid validates the assertion against the registered passkey credential so the
client-side flow can match production. In production, Turnkey validates the
WebAuthn assertion.
required:
- credentialId
- clientDataJson
Expand Down
Loading