POST /v1/auth/passkey/login/begin
Start a WebAuthn authentication ceremony. Returns
PublicKeyCredentialRequestOptions that the browser hands to
navigator.credentials.get().
::: tip Auth Required: none. :::
Request
POST /v1/auth/passkey/login/begin
| Header | Required | Notes |
|---|---|---|
Content-Type: application/json | yes | — |
Body
{ "email": "alice@example.com" }
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | yes (v0.1) | The user attempting to sign in. v0.1 requires email; discoverable (resident-key) login lands in a later release. |
Response
200 OK
A WebAuthn PublicKeyCredentialRequestOptions JSON object:
{
"publicKey": {
"rpId": "app.example.com",
"challenge": "...",
"timeout": 60000,
"userVerification": "preferred",
"allowCredentials": [
{ "type": "public-key", "id": "..." }
]
}
}
Error responses
| Status | Code | When |
|---|---|---|
| 400 | invalid_input | Email missing. |
| 401 | authentication_required | No such user, OR the user has no enrolled passkeys. (Same response shape — avoids enumeration.) |
| 500 | internal_error | WebAuthn build failure. |
Notes
- Challenge state is persisted in
passkey_challengeswith a 5-minute TTL. - The response intentionally hides whether the account exists. Failed
ceremonies for non-existent accounts and accounts-with-no-passkeys both
return
authentication_required.