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 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 a tau_pub_… publishable key in Authorization, or infers the app from a <slug>.tilldev.app host — 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 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/signinPassword sign-in. Answers a session, a TOTP challenge, or a push challenge.
POST /v1/signupCreate an account (403 on invite-only apps).
POST /v1/totp/verify-signinTrade a challenge + TOTP/backup code for a session.
POST /v1/push/signin/pollPoll a push approval until the Authenticator answers.
POST /v1/passkeys/signin/start · finishWebAuthn sign-in; a passkey assertion mints an mfa-true session.
04Catalog

Magic links + email verification

RouteAuthPurpose
POST /v1/magic/sendSend a sign-in link. The click-through lands on the hosted pages.
POST /v1/magic/consumeConsume the link token (hosted pages call this). Single-use, network-checked.
POST /v1/verify-emailConsume a verification token. Failures are 200 {ok:false} — no oracle.
POST /v1/verify-email/resendResend 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 · resetForgot-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/refreshRotate the token pair. Single-use refresh tokens; reuse revokes the family.
POST /v1/signoutRevoke 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