Sign-in¶
People sign in with a passkey and nothing else; there are no passwords. A passkey is bound to the address it was made at, so the ceremony runs on this server's own page in the browser, and the app collects the result with a one-time code bound to a PKCE challenge. An account gets its first passkey by signing up (where the operator allows it) or from a one-time ticket the operator issues; further passkeys from a ticket a signed-in person issues.
GET /passkey¶
The sign-in page
Unauthenticated
Callable with no credential.
The page every passkey ceremony runs on, opened in the system browser.
What it does is read from its address: mode=signin (the default) or
mode=signup, or a ticket to add a passkey to the account the
ticket names. An app passes its PKCE challenge and a state; when
the ceremony succeeds the page opens
revoked://auth?code=…&state=…, and the app redeems the code at
/api/passkeys/token.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
mode |
query | no | |
ticket |
query | no | |
challenge |
query | no | PKCE S256 challenge, base64url. |
state |
query | no |
Responses
| Status | Meaning |
|---|---|
200 |
The page. |
POST /api/passkeys/login/begin¶
Start a passkey sign-in
Unauthenticated
Callable with no credential.
Returns WebAuthn request options for a discoverable credential — no
account is named; the authenticator's answer says whose passkey it is.
Must be called at the server's own address (localhost on a
development machine, never a loopback IP). Rate-limited per IP.
Request body
application/json
| Field | Type | Notes |
|---|---|---|
challenge |
string | The app's PKCE S256 challenge; the code issued at the end is bound to it. |
Responses
| Status | Meaning |
|---|---|
200 |
The ceremony, good for five minutes. |
400 |
passkey_request_invalid or passkey_origin_invalid. |
429 |
Too many attempts. Password gates, probes and challenges are limited per client, so a slug cannot be brute-forced. |
Response fields
| Field | Type | Notes |
|---|---|---|
session |
string | |
options |
object | {publicKey: PublicKeyCredentialRequestOptions}, binary members base64url. |
POST /api/passkeys/login/finish¶
Finish a passkey sign-in
Unauthenticated
Callable with no credential.
Verifies the authenticator's assertion and returns a one-time code, good for two minutes, that only the holder of the PKCE verifier can redeem. A ceremony is answered once, right or wrong.
Request body
application/json
| Field | Type | Notes |
|---|---|---|
session |
string | |
credential |
object | The PublicKeyCredential, binary members base64url. |
Responses
| Status | Meaning |
|---|---|
200 |
The one-time code. |
400 |
passkey_ceremony_invalid, passkey_request_invalid or passkey_origin_invalid. |
401 |
passkey_verification_failed. |
429 |
Too many attempts. Password gates, probes and challenges are limited per client, so a slug cannot be brute-forced. |
Response fields
| Field | Type | Notes |
|---|---|---|
code |
string |
POST /api/passkeys/register/begin¶
Start registering a passkey
Unauthenticated
Callable with no credential.
Either a new account — email, where the operator allows signups — or
a ticket, which adds a passkey to the account it names. Returns
WebAuthn creation options that require a discoverable credential and
user verification. Nothing is written until the ceremony finishes.
Request body
application/json
| Field | Type | Notes |
|---|---|---|
email |
string | The new account's address. Ignored with a ticket. |
ticket |
string | A one-time ticket from /api/passkeys/tickets or the operator. |
name |
string | What the passkey is called in the owner's list. |
challenge |
string | The app's PKCE S256 challenge, when the result should sign an app in. |
Responses
| Status | Meaning |
|---|---|
200 |
The ceremony, good for five minutes. |
400 |
passkey_email_invalid, passkey_ticket_invalid, passkey_request_invalid or passkey_origin_invalid. |
403 |
signups_disabled: no ticket, and this server does not accept registrations. |
409 |
passkey_email_taken. |
429 |
Too many attempts. Password gates, probes and challenges are limited per client, so a slug cannot be brute-forced. |
Response fields
| Field | Type | Notes |
|---|---|---|
session |
string | |
options |
object | {publicKey: PublicKeyCredentialCreationOptions}, binary members base64url. |
POST /api/passkeys/register/finish¶
Finish registering a passkey
Unauthenticated
Callable with no credential.
Verifies the attestation, then writes the passkey — and, for a signup, the account — and spends the ticket. Returns a one-time code when the ceremony was started with a PKCE challenge.
Request body
application/json
| Field | Type | Notes |
|---|---|---|
session |
string | |
credential |
object | The PublicKeyCredential, binary members base64url. |
Responses
| Status | Meaning |
|---|---|
200 |
Saved. |
400 |
passkey_ceremony_invalid, passkey_verification_failed, passkey_ticket_invalid or passkey_origin_invalid. |
409 |
passkey_email_taken. |
429 |
Too many attempts. Password gates, probes and challenges are limited per client, so a slug cannot be brute-forced. |
Response fields
| Field | Type | Notes |
|---|---|---|
code |
string | Present when a PKCE challenge was given. |
POST /api/passkeys/token¶
Exchange a code for a session (the app)
Unauthenticated
Callable with no credential.
Spends the one-time code — right or wrong, a code works once — and, when the verifier matches its challenge, returns a session token. Not needed for API-key access.
Request body
application/json
| Field | Type | Notes |
|---|---|---|
code |
string | |
verifier |
string | The PKCE verifier. |
Responses
| Status | Meaning |
|---|---|
200 |
A session token and the user record. |
400 |
passkey_grant_invalid. |
429 |
Too many attempts. Password gates, probes and challenges are limited per client, so a slug cannot be brute-forced. |
Response fields
| Field | Type | Notes |
|---|---|---|
token |
string | |
record |
object |
POST /api/passkeys/tickets¶
Issue a link to add a passkey
Auth: Session
Returns a one-time link, good for fifteen minutes, to the sign-in page where the caller's account can register another passkey — on this device or, opened elsewhere, on another. Users only: an API key cannot let itself in as the person behind it.
Responses
| Status | Meaning |
|---|---|
200 |
The link. |
401 |
Refused, with a named reason. |
Response fields
| Field | Type | Notes |
|---|---|---|
url |
string | |
expiresAt |
string |
DELETE /api/passkeys/{id}¶
Remove a passkey
Auth: Session
Removes one of the caller's passkeys; it stops signing in at once. The
last one cannot be removed. Passkeys are listed through
/api/collections/passkeys/records (name, created, last used — never
the key).
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
id |
path | yes |
Responses
| Status | Meaning |
|---|---|
204 |
Removed. |
401 |
Refused, with a named reason. |
404 |
No such resource, or the caller may not see it. |
409 |
passkey_last. |