TILLAUTH · REFERENCE
The end-user API.
OpenAPI 3.1Everything an end user's browser calls — sign-in and sign-up, magic links, passwords, passkeys, TOTP, sessions. The hosted pages are clients of exactly this surface, and so is an embedded settings screen. The canonical description is machine-readable and served by the API itself.
01Spec
Fetch the specification
bash
curl https://auth.tilldev.dev/openapi.json
# same document, versioned path:
curl https://auth.tilldev.dev/v1/openapi.jsonServed with wildcard CORS like the OIDC discovery documents, so Swagger UI, Postman, and codegen tools can load it straight from the URL — no download step.
It cannot drift
The spec is asserted against the live route table in CI: a documented route that doesn't exist, or a renamed route the spec didn't follow, fails the service's test suite. What you read is what is mounted.
02Contract
Conventions the whole surface shares
- App resolution. Send
X-TillAuth-App: <app public id>. (The API also accepts atau_pub_…publishable key inAuthorization, or infers the app from a<slug>.tilldev.apphost — but routes that carry a user token occupy the Authorization header, so send the header.) - Realm strictness. Account routes verify the token was minted for this app. Cross-realm tokens get a plain 401.
- Non-enumeration. Email-accepting public routes (magic send, reset request, verify resend, signup on a taken email) answer success-shaped regardless of account existence, with padded timing. Practical consequence for
/v1/signup: a new account always returns the full bundle includinguser.id— even when verification is still pending (email_verified: false) — so the one response without an id is the taken-email case, which returns a bare{ ok: true }. If you are provisioning server-side and need an id for every address, don't drive it through this route; use the authenticated admin surface (import / SCIM), which returns the id and reports an existing account explicitly asconflict. The address's real owner gets an “you already have an account” email, so the human is never left at a dead end. - Refusals carry their reason. 403
step_up_requiredmeans “prove a factor, then retry”; 409 floor refusals name what to set up before removing a credential. Both are written to be shown to the user — handling guide. - Rate limits. 429 with
Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset.
03Catalog
Sign in, sign up
| Route | Auth | Purpose |
|---|---|---|
POST /v1/signin | — | Password sign-in. Answers a session, a TOTP challenge, or a push challenge. |
POST /v1/signup | — | Create an account (403 on invite-only apps). |
POST /v1/totp/verify-signin | — | Trade a challenge + TOTP/backup code for a session. |
POST /v1/push/signin/poll | — | Poll a push approval until the Authenticator answers. |
POST /v1/passkeys/signin/start · finish | — | WebAuthn sign-in; a passkey assertion mints an mfa-true session. |
04Catalog
Magic links + email verification
| Route | Auth | Purpose |
|---|---|---|
POST /v1/magic/send | — | Send a sign-in link. The click-through lands on the hosted pages. |
POST /v1/magic/consume | — | Consume the link token (hosted pages call this). Single-use, network-checked. |
POST /v1/verify-email | — | Consume a verification token. Failures are 200 {ok:false} — no oracle. |
POST /v1/verify-email/resend | — | Resend verification. Success-shaped always. |
05Catalog
Passwords, passkeys, TOTP
| Route | Auth | Purpose |
|---|---|---|
POST /v1/password/set | Bearer | First password for a password-less account. 409 if one exists. |
POST /v1/password/change | Bearer | Change; revokes every session on success. |
POST /v1/password/reset-request · reset | — | Forgot-password loop; the emailed link lands hosted. |
GET /v1/passkeys | Bearer | List passkeys, including counter-locked ones. |
POST /v1/passkeys/register/start · finish | Bearer | WebAuthn registration (step-up-gated when a factor exists). |
DELETE /v1/passkeys/:id | Bearer | Remove. 409 with a reason when it would strand the account. |
POST /v1/passkeys/:id/unlock | Bearer | Recover a counter-locked passkey after a device restore. |
POST /v1/totp/setup · verify-setup | Bearer | Enrol TOTP; verify returns backup codes + a fresh mfa-true pair. |
POST /v1/totp/disable | Bearer | Turn off. 409 when it would strand an MFA-required account. |
06Catalog
Account + sessions
| Route | Auth | Purpose |
|---|---|---|
POST /v1/me | Bearer | The user + factor inventory — the read model for a settings screen. |
POST /v1/refresh | — | Rotate the token pair. Single-use refresh tokens; reuse revokes the family. |
POST /v1/signout | — | Revoke the refresh family. Idempotent. |
GET /v1/sessions | Bearer | List sessions (device, IP, last seen), current first. |
POST /v1/sessions/revoke · revoke-others | Bearer | Kill one session, or every one but this. |
07Scope
What this spec deliberately excludes
| Surface | Described by |
|---|---|
| OIDC provider endpoints | /.well-known/openid-configuration — see Sign in with TillAuth |
| SCIM provisioning | RFC 7644 at /scim/v2 — see Provisioning |
| Admin surface (app + user management) | The dashboard API — dashboard-session authenticated |
| Till Authenticator device protocol | Signed mobile protocol (device enrolment, push approve/deny) — not a browser API |
Build with it: embedded account settings · React quickstart