The Grid sandbox accepts a small set of test helpers for Global Account flows, so you can exercise the full request shape without real OTP delivery, WebAuthn ceremony, or wallet signatures. Email OTP uses a sandbox test inbox. Passkey and wallet signatures use fixed sandbox-only values. OAuth accepts OIDC ID tokens from supported providers; for isolated sandbox tests, you can also pass a JWT-shaped test token. 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 does not send OTP emails. After creating an EMAIL_OTP credential or requesting a new email OTP challenge, call GET /sandbox/email-otps/latest with the auth method ID to retrieve the latest active code for that credential. Then pass the returned otpCode as the body otp on POST /auth/credentials/{id}/verify.
export PUBLIC_KEY="04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"
OTP_CODE=$(curl -sS "$GRID_BASE_URL/sandbox/email-otps/latest?authMethodId=AuthMethod:abc123" \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" | jq -r '.otpCode')
curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:abc123/verify" \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"type": "EMAIL_OTP",
"otp": "'"$OTP_CODE"'",
"clientPublicKey": "'"$PUBLIC_KEY"'"
}'The sandbox validates the code against the pending OTP state. A wrong, expired, consumed, or locked-out code returns 401 UNAUTHORIZED.
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.
# 1. /challenge with clientPublicKey
curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMethod:abc123/challenge \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"clientPublicKey": "04f45f2a..."
}'
# 2. /verify with the magic signature, no clientPublicKey
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" \
-H "Request-Id: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
-d '{
"type": "PASSKEY",
"assertion": {
"credentialId": "...",
"clientDataJson": "...",
"authenticatorData": "...",
"signature": "sandbox-valid-passkey-signature"
}
}'Any other signature returns 401 UNAUTHORIZED with reason: "Invalid passkey signature".
OAuth does not use a fixed magic token in sandbox. Pass the OIDC ID token from your supported provider as oidcToken. For isolated sandbox tests, you can generate a JWT-shaped test token yourself. The JWT signature segment can be a dummy value, but the payload must look like a real ID token.
For POST /auth/credentials with type: "OAUTH", the sandbox token must include:
iss: a supported issuer, such ashttps://accounts.google.com,accounts.google.com, orhttps://appleid.apple.comaud: a non-empty string, or a single-element string arraysub: a non-empty subject identifier for the useriat: a numeric issued-at timestamp no more than 60 seconds before the request, with 5 seconds of clock skew allowedexp: a numeric expiration timestamp later than the request time
Grid stores the OAuth credential's registered identity from iss, aud, and sub. On POST /auth/credentials/{id}/verify, the fresh oidcToken must carry the same iss, aud, and sub as the credential being verified. It must also include nonce equal to sha256(clientPublicKey), where clientPublicKey is the exact hex public key sent in the verify request.
export PUBLIC_KEY="04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"
OIDC_TOKEN=$(node - <<'NODE'
const crypto = require("crypto");
const publicKey = process.env.PUBLIC_KEY || "04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2";
const now = Math.floor(Date.now() / 1000);
const b64url = (value) =>
Buffer.from(JSON.stringify(value)).toString("base64url");
const payload = {
iss: "https://accounts.google.com",
sub: "sandbox-user-123",
aud: "your-google-client-id.apps.googleusercontent.com",
iat: now,
exp: now + 300,
nonce: crypto.createHash("sha256").update(publicKey).digest("hex"),
email: "sandbox-user-123@example.com",
email_verified: true
};
console.log(
`${b64url({ alg: "RS256", typ: "JWT" })}.${b64url(payload)}.sandbox-signature`
);
NODE
)
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" \
-H "Request-Id: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
-d '{
"type": "OAUTH",
"oidcToken": "'"$OIDC_TOKEN"'",
"clientPublicKey": "'"$PUBLIC_KEY"'"
}'Pass sandbox-valid-signature as the Grid-Wallet-Signature HTTP header on any signed-retry flow:
POST /auth/credentials(add-additional-credential signed retry)DELETE /auth/credentials/{id}(revoke credential)DELETE /auth/sessions/{id}(revoke session)POST /internal-accounts/{id}/export(export wallet)PATCH /internal-accounts/{id}(update wallet privacy)POST /quotes/{quoteId}/execute(when source is an embedded wallet)
curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/Quote:abc123/execute \
-u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
-H "Grid-Wallet-Signature: sandbox-valid-signature"Any other header value returns 401 UNAUTHORIZED with reason: "Invalid Grid-Wallet-Signature".