TILLAUTH · REFERENCE

The end-user API.

OpenAPI 3.1

Everything 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.json

Served 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 checked against the service's own route list: every route it documents exists, and a renamed route can't be left out. What you read is what is served.
02Contract

Conventions the whole surface shares

  • App resolution. Send X-TillAuth-App: <app public id>. (The API also accepts the app's publishable key, Authorization: Bearer tau_pub_…, shown next to the public id in the app's Settings, or infers the app from a <slug>.tilldev.app host. Routes that carry a user token occupy the Authorization header, so send the header there. A header and a publishable key that name different apps are refused.)
  • 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 including user.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 as conflict. 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_required means “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

RouteAuthPurpose
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

RouteAuthPurpose
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

RouteAuthPurpose
POST /v1/password/setBearerFirst password for a password-less account. 409 if one exists.
POST /v1/password/changeBearerChange; revokes every session on success.
POST /v1/password/reset-request · reset—Forgot-password loop; the emailed link lands hosted.
GET /v1/passkeysBearerList passkeys, including counter-locked ones.
POST /v1/passkeys/register/start · finishBearerWebAuthn registration (step-up-gated when a factor exists).
DELETE /v1/passkeys/:idBearerRemove. 409 with a reason when it would strand the account.
POST /v1/passkeys/:id/unlockBearerRecover a counter-locked passkey after a device restore.
POST /v1/totp/setup · verify-setupBearerEnrol TOTP; verify returns backup codes + a fresh mfa-true pair.
POST /v1/totp/disableBearerTurn off. 409 when it would strand an MFA-required account.
06Catalog

Account + sessions

RouteAuthPurpose
POST /v1/meBearerThe 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/sessionsBearerList sessions (device, IP, last seen), current first.
POST /v1/sessions/revoke · revoke-othersBearerKill one session, or every one but this.
07Scope

What this spec deliberately excludes

SurfaceDescribed by
OIDC provider endpoints/.well-known/openid-configuration — see Sign in with TillAuth
SCIM provisioningRFC 7644 at /scim/v2 — see Provisioning
Admin surface (app + user management)The dashboard API — dashboard-session authenticated
Till Authenticator device protocolSigned mobile protocol (device enrolment, push approve/deny) — not a browser API