Authentication
40 endpoints, generated from the API's own OpenAPI document. Sign-up, sign-in, verification, password reset and the second factor (better-auth). These answer `{code, message}` JSON, not problem documents, except where noted.
POST /v2/auth/sign-up/email
Create an account with email and password (Turnstile-guarded; a verification mail is sent)
Credentials: none. Operation id authSignUpEmail.
Anonymous. Creates the better-auth user, its credential, the Taigan account and the person's own organization (named from orgName, else from the address), records acceptance of the Terms and Privacy Policy versions in force (submitting the form is the acceptance; a legal.accepted audit row is written), stores the marketing choice (opted out unless marketingOptIn is true), and mails a verification link. No session is created: sign-in is refused until the address is verified (the link signs the person in). Requires a Cloudflare Turnstile token in the x-captcha-response header, checked by Taigan before better-auth sees the request (the token is read from the header only). Anti-enumeration: signing up with an address that already has a user answers the same 200 with a synthetic user and sends nothing; if that existing user is still UNVERIFIED, its stored password is discarded (AUTH-2), and when the real owner later verifies they are signed in and sent a password-reset link to choose one. Accepts application/json or application/x-www-form-urlencoded. Not the invitation sign-up: that is POST /v2/invites/sign-up.
Parameters
x-captcha-responseheader · stringrequired- The Cloudflare Turnstile widget token for this form submission.
Request body
Required; application/json.
namestringrequired- The person's name (becomes the account's display name).
emailstring (email)required- The sign-in address. Stored lower-cased.
passwordstringrequired- The password: at least 10 and at most 128 characters. (at most 128 characters)
orgNamestring- Name for the person's own organization. Optional; read once at creation (an auto name is used when absent).
marketingOptInboolean- True if the person ticked the marketing-consent box. Absent or false means opted out (O26 R2). (default false)
callbackURLstring- Where the verification link sends the browser after verifying (relative, or on a trusted origin). Defaults to
/. imagestring- A profile image URL. Stored, never shown.
rememberMeboolean- Accepted for compatibility; has no effect here because sign-up never creates a session.
Responses
200 success
Accepted. The same body whether or not the address was new.
As application/json.
tokennullalways present- Always null: no session is created until the address is verified.
userAuthUseralways present- The better-auth user (the identity behind a Taigan account; one to one with
accounts). Timestamps are ISO 8601 strings. user.idstring (uuid)always present- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestringalways present- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)always present- The sign-in address, lower-cased.
user.emailVerifiedbooleanalways present- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)always present- When the user was created.
user.updatedAtstring (date-time)always present- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable).
400 error
Bad request. From Taigan's Turnstile guard (problem+json): captcha_missing (T108: the problem's own code; errors: [{code: "captcha_missing"}] repeats it) (no x-captcha-response header). From better-auth (JSON {code, message}): VALIDATION_ERROR (a required field missing or the wrong type), INVALID_EMAIL, PASSWORD_TOO_SHORT (under 10), PASSWORD_TOO_LONG (over 128), BAD_REQUEST (malformed JSON).
As application/problem+json, application/json.
403 error
Forbidden. From Taigan's Turnstile guard (problem+json): captcha_failed (Cloudflare rejected the token). From better-auth (JSON): INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (the request carries an Origin, Referer or cookie and the origin is not one of the app's trusted origins), CROSS_SITE_NAVIGATION_LOGIN_BLOCKED (a cross-site form navigation), INVALID_CALLBACK_URL (callbackURL not on a trusted origin).
As application/problem+json, application/json.
415 error
Unsupported media type (better-auth JSON). Code: UNSUPPORTED_MEDIA_TYPE (the body is neither JSON nor form-encoded).
As application/json.
422 error
Unprocessable (better-auth JSON). Code: FAILED_TO_CREATE_USER (the user could not be written).
As application/json.
429 error
better-auth's request limiter (production): 3 sign-ups per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
X-Retry-Afterheader- Seconds to wait (better-auth's own header;
Retry-Aftercarries the same value).
503 error
Service unavailable (problem+json). Code: unavailable (Taigan could not reach Cloudflare's Turnstile verification service).
As application/problem+json.
Problem codes: captcha_missing (400), captcha_failed (403), unavailable (503). Every problem is a problem document.
POST /v2/auth/sign-in/email
Sign in with email and password (sets the session cookie, or starts a second-factor challenge)
Credentials: none. Operation id authSignInEmail.
Anonymous. On success sets the session cookie (dtg.session_token, __Secure- prefixed on https; HttpOnly, SameSite=Lax) and answers the user. Creating the session deletes every other session of the person (one active session per person, newest wins; an auth.session_evicted audit row is written when one was ended), records the device (an auth.signin audit row; a 'new sign-in from a new device' mail and bell notice when a device appears after others are known) and stamps presence (which cancels an open ownership claim on an organization the person owns). If the person has a live second factor, no session is created: the answer is {twoFactorRedirect: true, twoFactorMethods} and a challenge cookie (dtg.two_factor, 10 minutes) is set; finish with POST /v2/auth/two-factor/verify-totp or /two-factor/verify-backup-code (a device trusted within 30 days skips the challenge). Every refused attempt writes an auth.signin_failed audit row naming a hash of the address. Taigan's per-address throttle counts every attempt and is cleared by a success. Accepts application/json or application/x-www-form-urlencoded. API integrations do not sign in: they use an API key (Authorization: Bearer).
Request body
Required; application/json.
emailstring (email)required- The sign-in address (case-insensitive).
passwordstringrequired- The password.
rememberMeboolean- False makes the session cookie a browser-session cookie (no Max-Age) and sets
dtg.dont_remember; the server-side session still slides as usual. (default true) callbackURLstring- If given, the answer carries
redirect: true,urland aLocationheader with this URL (still status 200). Must be relative or on a trusted origin.
Responses
200 success
Signed in, or a second-factor challenge started.
As application/json.
Locationheader- The
callbackURL, when one was sent and the sign-in completed.
redirectboolean- True when
callbackURLwas sent (the client should navigate tourl). tokenstring- The new session's token (also in the session cookie).
urlstring or null- The
callbackURL, or absent/null. userAuthUser- The better-auth user (the identity behind a Taigan account; one to one with
accounts). Timestamps are ISO 8601 strings. user.idstring (uuid)always present- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestringalways present- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)always present- The sign-in address, lower-cased.
user.emailVerifiedbooleanalways present- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)always present- When the user was created.
user.updatedAtstring (date-time)always present- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable). twoFactorRedirecttrue- Always true: the password was right and a second factor is required. No session cookie was set; the
dtg.two_factorchallenge cookie was. twoFactorMethodsarray of "totp" | "otp"- The factors that can answer the challenge. In this deployment
["totp"](a backup code is always accepted too);otpnever appears because mailed codes are not configured.
400 error
Bad request (better-auth JSON). Codes: VALIDATION_ERROR (email or password missing or not a string), INVALID_EMAIL (not an address), BAD_REQUEST (malformed JSON).
As application/json.
401 error
Unauthorized (better-auth JSON). Codes: INVALID_EMAIL_OR_PASSWORD (no such user, no password set, or the wrong password: one answer for all three), FAILED_TO_CREATE_SESSION.
As application/json.
403 error
Forbidden (better-auth JSON). Codes: EMAIL_NOT_VERIFIED (the password was right but the address is not verified yet; no mail is re-sent, use /v2/auth/send-verification-email), INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (origin not trusted), CROSS_SITE_NAVIGATION_LOGIN_BLOCKED, INVALID_CALLBACK_URL.
As application/json.
415 error
Unsupported media type (better-auth JSON). Code: UNSUPPORTED_MEDIA_TYPE.
As application/json.
429 error
Too many requests. Two different refusals, both with Retry-After: Taigan's per-address throttle, code signin_throttled (more than 10 attempts for one address in an hour; body AuthSigninThrottled), or better-auth's per-IP limiter in production (3 sign-ins per 10 seconds; body {message} with no code, plus X-Retry-After).
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
Problem codes: signin_throttled (429). Every problem is a problem document.
POST /v2/auth/sign-in/social
Start (or complete with an ID token) a social sign-in. Not configured in this deployment
Credentials: none. Operation id authSignInSocial.
Anonymous. better-auth's OAuth sign-in. No social provider is configured in this deployment (Google is dormant until GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET are set, and O27 rules it off at launch), so every request that names a valid provider answers 404 PROVIDER_NOT_FOUND. Were a provider configured, the redirect form answers {url, redirect} with a Location header for the browser to follow to the provider (status 200), and the idToken form verifies the provider's ID token and signs in directly. A browser flow; an API client never needs it.
Request body
Required; application/json.
providerstringrequired- The provider id (one of better-auth's known providers, for example
google). An unknown name is a 400VALIDATION_ERROR; a known but unconfigured one is a 404. callbackURLstring- Where to send the browser after a successful sign-in (relative or trusted origin).
newUserCallbackURLstring- Where to send a newly created user instead of
callbackURL. errorCallbackURLstring- Where to send the browser if the flow fails.
disableRedirectboolean- True to get the provider URL back without a
Locationheader. idTokenobject- An ID token already obtained from the provider, to sign in without a redirect (providers that support it).
idToken.tokenstringrequired- The provider's ID token.
idToken.noncestring- The nonce used when the token was issued.
idToken.accessTokenstring- The provider access token, if any.
idToken.refreshTokenstring- The provider refresh token, if any.
idToken.expiresAtnumber- Token expiry (epoch).
idToken.userobject- Profile data some providers send only once (Apple).
idToken.user.nameobject- The person's name parts.
idToken.user.name.firstNamestring- Given name.
idToken.user.name.lastNamestring- Family name.
idToken.user.emailstring- The address the provider reported.
scopesarray of string- Scopes to request instead of the provider defaults.
requestSignUpboolean- Explicitly allow creating a user when the provider disables implicit sign-up.
loginHintstring- A login hint forwarded to the provider.
additionalParamsobject- Extra query parameters for the provider's authorization URL (reserved OAuth keys are refused).
additionalDataobject- Opaque data carried through the OAuth state.
Responses
200 success
Redirect form: {url, redirect}. ID-token form: {redirect: false, token, user} and the session cookie. Not reachable in this deployment.
As application/json.
redirectbooleanalways present- True when a
Locationheader to the provider was set. urlstring- The provider's authorization URL (redirect form).
tokenstring- The new session token (ID-token form).
userAuthUser- The better-auth user (the identity behind a Taigan account; one to one with
accounts). Timestamps are ISO 8601 strings. user.idstring (uuid)always present- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestringalways present- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)always present- The sign-in address, lower-cased.
user.emailVerifiedbooleanalways present- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)always present- When the user was created.
user.updatedAtstring (date-time)always present- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable).
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (missing or unknown provider, bad field types).
As application/json.
401 error
Unauthorized (better-auth JSON). Codes (ID-token form, only with a provider configured): INVALID_TOKEN, FAILED_TO_GET_USER_INFO, USER_EMAIL_NOT_FOUND, OAUTH_LINK_ERROR.
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (a cookie-carrying request from an untrusted origin), INVALID_CALLBACK_URL, INVALID_ERROR_CALLBACK_URL, INVALID_NEW_USER_CALLBACK_URL, EMAIL_NOT_VERIFIED (ID-token form).
As application/json.
404 error
Not found (better-auth JSON). Codes: PROVIDER_NOT_FOUND (always, in this deployment), ID_TOKEN_NOT_SUPPORTED.
As application/json.
429 error
better-auth's request limiter (production): 3 per 10 seconds per IP on /sign-in/*. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/sign-out
Sign out: end the current session and clear the session cookie
Credentials: App session. Operation id authSignOut.
Deletes the session named by the cookie (if any) and expires the session cookies. Succeeds without a session too (nothing to end). Other /v2 routes cache a session lookup for up to 15 seconds, so the ended cookie can still be honored there for that long. The optional body only matters for a social provider with an end-session URL, which this deployment does not have.
Request body
Optional; application/json.
callbackURLstring- Post-logout redirect for a provider logout (unused here).
disableRedirectboolean- Return a provider logout URL without a
Locationheader (unused here). statestring- State passed to a provider logout endpoint (unused here).
Responses
200 success
Signed out.
As application/json.
successbooleanalways present- Always true.
urlstring- A provider logout URL. Never present in this deployment.
redirectboolean- Whether to follow
url. Never present in this deployment.
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (body fields of the wrong type).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (a cookie-carrying POST must come from a trusted origin), INVALID_CALLBACK_URL.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/get-session
The current session and user, or null
Credentials: App session. Operation id authGetSession.
Reads the session cookie. Answers null (status 200) when there is no cookie, or the session has expired or been revoked (an expired session's row and cookie are then deleted). Extends a session used more than a day after its last extension (a fresh expiresAt 7 days out and a re-set cookie). Answers Cache-Control: no-store. Note this is better-auth's view: the rest of /v2 additionally refuses a session 30 days after sign-in, an unverified user or a closed account, so a non-null answer here does not guarantee /v2/account will accept the cookie. Kelpie uses it to restore a signed-in tab.
Parameters
disableRefreshquery · boolean- True to read the session without extending it.
Responses
200 success
The session and user, or null when signed out.
As application/json.
sessionAuthSession- A better-auth session row. Taigan keeps one active session per person: creating a session deletes every other session of that person (ORG_LICENSING 4.2), so lists hold at most one entry.
session.idstring (uuid)always present- The session's id (the same id
GET /v2/account/sessionsreports). session.tokenstringalways present- The session's bearer secret: the unsigned value inside the session cookie. Treat it as a credential.
POST /v2/auth/revoke-sessiontakes it. session.userIdstring (uuid)always present- The auth user's id.
session.expiresAtstring (date-time)always present- When the session expires if not used. Sliding: a use more than a day after the last extension moves it to 7 days (
SESSION_SLIDING_DAYS) from then. Taigan also ends every session 30 days (SESSION_MAX_DAYS) aftercreatedAtwhatever this says. session.createdAtstring (date-time)always present- When the session was created (the sign-in).
session.updatedAtstring (date-time)always present- When the session was last extended.
session.ipAddressstring or null- The client IP that created the session, as Taigan's proxy configuration resolved it.
session.userAgentstring or null- The User-Agent that created the session.
userAuthUser- The better-auth user (the identity behind a Taigan account; one to one with
accounts). Timestamps are ISO 8601 strings. user.idstring (uuid)always present- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestringalways present- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)always present- The sign-in address, lower-cased.
user.emailVerifiedbooleanalways present- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)always present- When the user was created.
user.updatedAtstring (date-time)always present- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable).
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (a query flag that is not a boolean).
As application/json.
401 error
Unauthorized (better-auth JSON). Code: FAILED_TO_GET_SESSION (the session vanished while being extended; the cookie is cleared).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
500 error
Server error (better-auth JSON). Code: FAILED_TO_GET_SESSION.
As application/json.
POST /v2/auth/get-session
POST form of get-session. Always refused in this deployment
Credentials: App session. Operation id authGetSessionPost.
better-auth serves POST /get-session only when session.deferSessionRefresh is on, which Taigan does not set, so a well-formed request always answers 405 METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED. Use GET /v2/auth/get-session.
Parameters
disableRefreshquery · boolean- As on the GET form (never reached).
Responses
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (a cookie-carrying POST from an untrusted origin is refused before the method check).
As application/json.
405 error
Method not allowed (better-auth JSON). Code: METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED (always).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/verify-email
The mailed verification link: verifies the address (or a change of address) and signs the person in
Credentials: App session. Operation id authVerifyEmail.
Anonymous; a browser follows it from the verification mail (through the app's /v2 proxy). The token is a signed link valid for EMAIL_VERIFICATION_LINK_SECONDS (1 hour). Three cases. (1) Sign-up verification: marks the address verified, stamps the account verified, and signs the person in (sets the session cookie; the session-create effects of sign-in apply). If a contested sign-up discarded the password (AUTH-2), a password-reset link is mailed so the person can choose one. An already-verified address changes nothing and creates no session. (2) Change of address (from POST /v2/auth/change-email): moves the sign-in address to the new one, verified, and sets a session. Membership is never touched. (3) Errors: with callbackURL the browser is redirected to callbackURL?error=<CODE> (with &lifetime_seconds= for TOKEN_EXPIRED and INVALID_TOKEN); without it the error is a JSON 401. A browser uses this; an API client never needs it.
Parameters
tokenquery · stringrequired- The verification token from the mailed link.
callbackURLquery · string- Where to send the browser afterwards (relative or on a trusted origin). Without it the answer is JSON.
Responses
200 success
Verified (no callbackURL). user is null for a sign-up verification or an already-verified address, and the updated user for a change of address.
As application/json.
statusbooleanalways present- Always true.
userAuthUser or nullalways present- The user after a change of address; null otherwise.
user.idstring (uuid)- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestring- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)- The sign-in address, lower-cased.
user.emailVerifiedboolean- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)- When the user was created.
user.updatedAtstring (date-time)- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable).
302 redirect
Redirect to callbackURL on success, or to callbackURL?error=<CODE> on failure (TOKEN_EXPIRED or INVALID_TOKEN with &lifetime_seconds=<seconds>, USER_NOT_FOUND, INVALID_USER).
Locationheader- The destination.
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (no token).
As application/json.
401 error
Unauthorized (no callbackURL; better-auth JSON). Codes: TOKEN_EXPIRED and INVALID_TOKEN (both with lifetime_seconds, body AuthLinkExpired), USER_NOT_FOUND (the address no longer has a user), INVALID_USER (a change-of-address link opened while signed in as someone else).
As application/json.
403 error
Forbidden (better-auth JSON). Code: INVALID_CALLBACK_URL (callbackURL is not relative and not on a trusted origin).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
500 error
Server error (better-auth JSON). Code: FAILED_TO_CREATE_SESSION.
As application/json.
POST /v2/auth/send-verification-email
Send a new verification link
Credentials: App session. Operation id authSendVerificationEmail.
Anonymous (the 'Send a new link' button after sign-up or after EMAIL_NOT_VERIFIED). Without a session: always answers {status: true} after at least half a second, whether or not the address exists; a mail is sent only when the address has an unverified user. With a session: the address must be the session's own and still unverified, which a Taigan session never is (sign-in requires verification), so a signed-in call answers EMAIL_ALREADY_VERIFIED. The new link is valid for EMAIL_VERIFICATION_LINK_SECONDS (1 hour).
Request body
Required; application/json.
emailstring (email)required- The address to send the link to.
callbackURLstring- Where the link sends the browser after verifying (relative or trusted origin). Defaults to
/.
Responses
200 success
Accepted (the same answer whether or not a mail was sent).
As application/json.
statusbooleanalways present- Always true on success.
400 error
Bad request (better-auth JSON). Codes: VALIDATION_ERROR (missing or malformed email), EMAIL_MISMATCH (signed in, and email is not the session's address), EMAIL_ALREADY_VERIFIED (signed in).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (cookie-carrying POST from an untrusted origin), INVALID_CALLBACK_URL.
As application/json.
429 error
better-auth's request limiter (production): 3 per 60 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/request-password-reset
Mail a password-reset link (Turnstile-guarded)
Credentials: none. Operation id authRequestPasswordReset.
Anonymous. Requires a Turnstile token in the x-captcha-response header (checked by Taigan first). Answers the same 200 whether or not the address has a user (anti-enumeration); when it does, a reset link valid for PASSWORD_RESET_LINK_SECONDS (1 hour) is mailed. The link is GET /v2/auth/reset-password/{token}?callbackURL=<redirectTo>, which sends the browser to redirectTo?token=...; the page then calls POST /v2/auth/reset-password.
Parameters
x-captcha-responseheader · stringrequired- The Cloudflare Turnstile widget token for this form submission.
Request body
Required; application/json.
emailstring (email)required- The account's address.
redirectTostring- The app page that sets the new password (Kelpie:
<app>/reset). Relative or on a trusted origin. The mailed link sends the browser there with?token=(or?error=INVALID_TOKEN&lifetime_seconds=when the link is spent or expired).
Responses
200 success
Accepted.
As application/json.
statusbooleanalways present- Always true.
messagestringalways presentIf this email exists in our system, check your email for the reset link.
400 error
Bad request. From Taigan's Turnstile guard (problem+json): captcha_missing (T108: the problem's own code; errors: [{code: "captcha_missing"}] repeats it). From better-auth (JSON): VALIDATION_ERROR (missing or malformed email).
As application/problem+json, application/json.
403 error
Forbidden. From Taigan's Turnstile guard (problem+json): captcha_failed. From better-auth (JSON): INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (cookie-carrying POST from an untrusted origin), INVALID_REDIRECT_URL / INVALID_CALLBACK_URL (redirectTo not relative and not on a trusted origin).
As application/problem+json, application/json.
429 error
better-auth's request limiter (production): 3 per 60 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
503 error
Service unavailable (problem+json). Code: unavailable (Cloudflare's Turnstile verification could not be reached).
As application/problem+json.
Problem codes: captcha_missing (400), captcha_failed (403), unavailable (503). Every problem is a problem document.
GET /v2/auth/reset-password/{token}
The mailed reset link: checks the token and redirects to the app's reset page
Credentials: none. Operation id authResetPasswordLink.
Anonymous; a browser follows it from the reset mail. Never changes anything: it redirects to callbackURL?token=<token> when the token is live, or to callbackURL?error=INVALID_TOKEN&lifetime_seconds=<seconds> when it is unknown, spent or expired. A browser uses this; an API client never needs it.
Parameters
tokenpath · stringrequired- The reset token from the mailed link.
callbackURLquery · stringrequired- The app page that sets the new password (relative or on a trusted origin); the mail fills it from
redirectTo.
Responses
302 redirect
Redirect to callbackURL?token=<token>, or to callbackURL?error=INVALID_TOKEN&lifetime_seconds=<PASSWORD_RESET_LINK_SECONDS>.
Locationheader- The destination.
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (no callbackURL).
As application/json.
403 error
Forbidden (better-auth JSON). Code: INVALID_CALLBACK_URL (not relative and not on a trusted origin).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/reset-password
Set a new password with a reset token
Credentials: none. Operation id authResetPassword.
Anonymous (the reset page posts the token it was redirected with). Consumes the token (one use), sets the password (creating the email-and-password credential if the account had none, as after an AUTH-2 discard), mails a 'your password was changed' confirmation, writes a bell notice saying other sessions were signed out, and deletes every session of the person. It revokes no API key: the mail and the notice list the person's active keys (name, prefix and last four, organization, created, last used) and say to revoke any that is not theirs. It does not sign the person in: they sign in with the new password. Not Turnstile-guarded.
Parameters
tokenquery · string- The reset token, if not sent in the body (the body wins).
Request body
Required; application/json.
newPasswordstringrequired- The new password: 10 to 128 characters. (at most 128 characters)
tokenstring- The reset token from the redirect's
?token=.
Responses
200 success
Password set; every session ended.
As application/json.
statusbooleanalways present- Always true on success.
400 error
Bad request (better-auth JSON). Codes: INVALID_TOKEN (no token, or unknown, spent or expired: indistinguishable; carries lifetime_seconds, body AuthLinkExpired), PASSWORD_TOO_SHORT, PASSWORD_TOO_LONG, USER_NOT_FOUND (the user was deleted after the mail), VALIDATION_ERROR (no newPassword).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (cookie-carrying POST from an untrusted origin).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/change-password
Change the password (current password required)
Credentials: App session. Operation id authChangePassword.
Session only. Checks currentPassword, then sets newPassword. With revokeOtherSessions: true every session is deleted and a new one is created and set in the cookie (its token is returned). No mail or notice is sent for a change (only a reset sends them). Kelpie does not call this today.
Request body
Required; application/json.
currentPasswordstringrequired- The current password.
newPasswordstringrequired- The new password: 10 to 128 characters. (at most 128 characters)
revokeOtherSessionsboolean- True to end every session and start a fresh one for this browser.
Responses
200 success
Password changed.
As application/json.
tokenstring or nullalways present- The new session's token when
revokeOtherSessionswas true; otherwise null. userAuthUseralways present- The better-auth user (the identity behind a Taigan account; one to one with
accounts). Timestamps are ISO 8601 strings. user.idstring (uuid)always present- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestringalways present- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)always present- The sign-in address, lower-cased.
user.emailVerifiedbooleanalways present- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)always present- When the user was created.
user.updatedAtstring (date-time)always present- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable).
400 error
Bad request (better-auth JSON). Codes: INVALID_PASSWORD (currentPassword is wrong), PASSWORD_TOO_SHORT, PASSWORD_TOO_LONG, CREDENTIAL_ACCOUNT_NOT_FOUND (the account has no password yet; use the reset flow), VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (the POST must come from a trusted origin).
As application/json.
429 error
better-auth's request limiter (production): 3 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
500 error
Server error (better-auth JSON). Code: FAILED_TO_GET_SESSION (the replacement session could not be created).
As application/json.
POST /v2/auth/change-email
Request a change of sign-in address (verified at the new address)
Credentials: App session. Operation id authChangeEmail.
Session only. Nothing changes yet: a verification link is mailed to the NEW address, and a notice naming the new address is mailed to the OLD one at once. The address moves when the link (GET /v2/auth/verify-email) is followed, valid for EMAIL_VERIFICATION_LINK_SECONDS; accounts.email follows it. Membership is never affected. If the new address already belongs to another user, the answer is the same {status: true} and nothing is sent (anti-enumeration).
Request body
Required; application/json.
newEmailstring (email)required- The address to move to (stored lower-cased).
callbackURLstring- Where the verification link sends the browser afterwards (relative or trusted origin). Defaults to
/.
Responses
200 success
Accepted.
As application/json.
statusbooleanalways present- Always true on success.
400 error
Bad request (better-auth JSON). Codes: VALIDATION_ERROR (missing or malformed newEmail); no code, message Email is the same (the new address is the current one).
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN, INVALID_CALLBACK_URL.
As application/json.
429 error
better-auth's request limiter (production): 3 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/update-user
Update the auth user's name (and other user fields)
Credentials: App session. Operation id authUpdateUser.
Session only. Sets any of name, image, orgName, marketingOptIn on the auth user; name is mirrored into the account's display_name. marketingOptIn is the person's consent to release notes and product news: it is stored as the account's marketing_opt_out (its opposite), which is what every mail reads and what GET /v2/account/profile shows, in both directions, and a change writes an account.marketing_changed audit row. orgName here only changes the auth user's copy, which nothing reads after sign-up, so it renames no organization (use PATCH /v2/account/organization). The address cannot be changed here (use /v2/auth/change-email).
Request body
Required; application/json.
namestring- The person's name (mirrored to
display_name). imagestring or null- A profile image URL (stored, never shown).
orgNamestring- Overwrites the sign-up form's copy only; renames nothing.
marketingOptInboolean- Consent to release notes and product news; stored as the account's
marketing_opt_out(true here ismarketing_opt_out: false), audited asaccount.marketing_changedwhen it changes.
Responses
200 success
Updated (the session cookie is re-set).
As application/json.
statusbooleanalways present- Always true on success.
400 error
Bad request (better-auth JSON). Codes: EMAIL_CAN_NOT_BE_UPDATED (email sent), FIELD_NOT_ALLOWED (twoFactorEnabled sent), BODY_MUST_BE_AN_OBJECT, VALIDATION_ERROR; no code, message No fields to update (none of the four fields sent).
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/update-session
Update custom session fields. None exist in this deployment
Credentials: App session. Operation id authUpdateSession.
Session only. better-auth lets a deployment declare extra session fields and set them here; Taigan declares none, so every body (with a live session) answers 400 with the message No fields to update.
Request body
Required; application/json.
Responses
200 success
Never answered in this deployment. Would be {session}.
As application/json.
sessionAuthSessionalways present- A better-auth session row. Taigan keeps one active session per person: creating a session deletes every other session of that person (ORG_LICENSING 4.2), so lists hold at most one entry.
session.idstring (uuid)always present- The session's id (the same id
GET /v2/account/sessionsreports). session.tokenstringalways present- The session's bearer secret: the unsigned value inside the session cookie. Treat it as a credential.
POST /v2/auth/revoke-sessiontakes it. session.userIdstring (uuid)always present- The auth user's id.
session.expiresAtstring (date-time)always present- When the session expires if not used. Sliding: a use more than a day after the last extension moves it to 7 days (
SESSION_SLIDING_DAYS) from then. Taigan also ends every session 30 days (SESSION_MAX_DAYS) aftercreatedAtwhatever this says. session.createdAtstring (date-time)always present- When the session was created (the sign-in).
session.updatedAtstring (date-time)always present- When the session was last extended.
session.ipAddressstring or null- The client IP that created the session, as Taigan's proxy configuration resolved it.
session.userAgentstring or null- The User-Agent that created the session.
400 error
Bad request (better-auth JSON). Codes: BODY_MUST_BE_AN_OBJECT, VALIDATION_ERROR; no code, message No fields to update (always, for an object body).
As application/json.
401 error
Unauthorized (better-auth JSON). Codes: UNAUTHORIZED (no live session), FAILED_TO_GET_SESSION.
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/verify-password
Check the signed-in person's password
Credentials: App session. Operation id authVerifyPassword.
Session only. Answers {status: true} if password is the person's current password; changes nothing. better-auth marks it server-scoped for its own client, but the router serves it over HTTP.
Request body
Required; application/json.
passwordstringrequired- The password to check.
Responses
200 success
The password is correct.
As application/json.
statusbooleanalways present- Always true on success.
400 error
Bad request (better-auth JSON). Codes: INVALID_PASSWORD (wrong, or the account has no password), VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/delete-user
Delete the user. Disabled in this deployment (use POST /v2/account/close)
Credentials: App session. Operation id authDeleteUser.
Session only. better-auth's self-deletion is not enabled in Taigan (user.deleteUser is not configured), so a signed-in request answers 404 with an empty body. Closing an account is POST /v2/account/close, which handles memberships, keys, billing and the audit trail.
Request body
Optional; application/json.
passwordstring- The current password (would be checked if the feature were on).
tokenstring- A deletion-confirmation token (unused here).
callbackURLstring- Where a confirmation link would send the browser (unused here).
Responses
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN, INVALID_CALLBACK_URL.
As application/json.
404 error
Not found: the feature is disabled. Always, for a signed-in caller. Empty body.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/delete-user/callback
Deletion-confirmation link. Disabled in this deployment
Credentials: none. Operation id authDeleteUserCallback.
The link better-auth would mail to confirm a self-deletion. Disabled here like /delete-user: a request with a token answers 404 NOT_FOUND. A browser link; an API client never needs it.
Parameters
tokenquery · stringrequired- The deletion-confirmation token.
callbackURLquery · string- Where to send the browser afterwards (relative or trusted origin).
Responses
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (no token).
As application/json.
403 error
Forbidden (better-auth JSON). Code: INVALID_CALLBACK_URL.
As application/json.
404 error
Not found (better-auth JSON). Code: NOT_FOUND (always: the feature is disabled).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/list-sessions
List the person's active sessions (better-auth's view)
Credentials: App session. Operation id authListSessions.
Session only, and the session must be fresh (created within the last 24 hours), else 403 SESSION_NOT_FRESH. Lists unexpired sessions, each with its token. Under Taigan's one-session policy the list holds the current session only. The Taigan surface for this (with current, IP and user agent, working for API keys too) is GET /v2/account/sessions.
Responses
200 success
The sessions.
As application/json.
[].idstring (uuid)always present- The session's id (the same id
GET /v2/account/sessionsreports). [].tokenstringalways present- The session's bearer secret: the unsigned value inside the session cookie. Treat it as a credential.
POST /v2/auth/revoke-sessiontakes it. [].userIdstring (uuid)always present- The auth user's id.
[].expiresAtstring (date-time)always present- When the session expires if not used. Sliding: a use more than a day after the last extension moves it to 7 days (
SESSION_SLIDING_DAYS) from then. Taigan also ends every session 30 days (SESSION_MAX_DAYS) aftercreatedAtwhatever this says. [].createdAtstring (date-time)always present- When the session was created (the sign-in).
[].updatedAtstring (date-time)always present- When the session was last extended.
[].ipAddressstring or null- The client IP that created the session, as Taigan's proxy configuration resolved it.
[].userAgentstring or null- The User-Agent that created the session.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Code: SESSION_NOT_FRESH (the session is more than 24 hours old; sign in again).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
500 error
Server error (better-auth JSON, INTERNAL_SERVER_ERROR status text): the sessions could not be read.
POST /v2/auth/revoke-session
End one of the person's sessions by its token
Credentials: App session. Operation id authRevokeSession.
Session only. Deletes the session whose token is given, if it is the caller's; a token that is unknown or someone else's is ignored and the answer is the same {status: true}. Revoking the current session's token signs this browser out (the cookie stays until it is next refused). Other /v2 routes may honor an ended session for up to 15 seconds (their lookup cache). The Taigan equivalent by session id is DELETE /v2/account/sessions/{id}.
Request body
Required; application/json.
tokenstringrequired- The session token to end (from
/v2/auth/list-sessions).
Responses
200 success
Done (or nothing to do).
As application/json.
statusbooleanalways present- Always true on success.
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (no token).
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
500 error
Server error (better-auth JSON). Code: INTERNAL_SERVER_ERROR (the delete failed).
As application/json.
POST /v2/auth/revoke-sessions
End every session of the person, this one included
Credentials: App session. Operation id authRevokeSessions.
Session only. Deletes all of the person's sessions, the current one included (the browser is signed out; the cookie is not cleared but is refused from then on, within 15 seconds on other /v2 routes). The Taigan equivalent is POST /v2/account/sessions/sign-out-everywhere.
Responses
200 success
Every session ended.
As application/json.
statusbooleanalways present- Always true on success.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
500 error
Server error (better-auth JSON). Code: INTERNAL_SERVER_ERROR.
As application/json.
POST /v2/auth/revoke-other-sessions
End every session of the person except this one
Credentials: App session. Operation id authRevokeOtherSessions.
Session only. Deletes the person's other unexpired sessions and keeps the current one. Under Taigan's one-session policy there are normally none to end (every sign-in already evicts the others), so this is usually a no-op that answers {status: true}.
Responses
200 success
Done.
As application/json.
statusbooleanalways present- Always true on success.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/list-accounts
List the sign-in methods linked to the person
Credentials: App session. Operation id authListAccounts.
Session only. In this deployment the list holds one entry, providerId: "credential" (email and password), because no social provider is configured. An account whose password was discarded (AUTH-2) still has the entry.
Responses
200 success
The linked sign-in methods.
As application/json.
[].idstring (uuid)always present- The linked account's id (what
accountIdmeans in/unlink-account,/get-access-token,/refresh-tokenand/account-info). [].providerIdstringalways presentcredentialfor email and password (the only kind this deployment creates), or a social provider id such asgoogle.[].accountIdstringalways present- The provider's own id for the person (for
credential, the auth user's id). [].issuerstringalways present- The issuer the provider id is scoped to (a local issuer for
credential). [].userIdstring (uuid)always present- The auth user's id.
[].scopesarray of stringalways present- OAuth scopes granted by the provider; empty for
credential. [].createdAtstring (date-time)always present- When the method was linked.
[].updatedAtstring (date-time)always present- When it was last changed (for
credential, the last password change).
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/link-social
Link a social sign-in to the person. Not configured in this deployment
Credentials: App session. Operation id authLinkSocial.
Session only. No social provider is configured, so a request naming a valid provider answers 404 PROVIDER_NOT_FOUND. Were one configured, the redirect form answers {url, redirect} (with a Location header) and the provider returns the browser to /v2/auth/callback/{id}; the ID-token form links directly and answers {url: "", status: true, redirect: false}.
Request body
Required; application/json.
providerstringrequired- The provider id (one of better-auth's known providers). Unknown names are a 400.
callbackURLstring- Where to send the browser after linking.
errorCallbackURLstring- Where to send the browser if linking fails.
disableRedirectboolean- Return the provider URL without a
Locationheader. idTokenobject- A provider ID token, to link without a redirect.
idToken.tokenstringrequired- The provider's ID token.
idToken.noncestring- The nonce used when it was issued.
idToken.accessTokenstring- The provider access token, if any.
idToken.refreshTokenstring- The provider refresh token, if any.
scopesarray of string- Additional scopes to request.
requestSignUpboolean- Allow sign-up where the provider disables implicit sign-up.
loginHintstring- A login hint forwarded to the provider.
additionalParamsobject- Extra query parameters for the provider's authorization URL.
additionalDataobject- Opaque data carried through the OAuth state.
Responses
200 success
Not reachable in this deployment.
As application/json.
urlstring- The provider authorization URL (redirect form), or empty (ID-token form).
redirectbooleanalways present- True when a
Locationheader was set. statusboolean- True when linked directly (ID-token form).
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (missing or unknown provider).
As application/json.
401 error
Unauthorized (better-auth JSON). Codes: UNAUTHORIZED (no live session); with a provider configured also INVALID_TOKEN, FAILED_TO_GET_USER_INFO, USER_EMAIL_NOT_FOUND, LINKING_NOT_ALLOWED, LINKING_DIFFERENT_EMAILS_NOT_ALLOWED.
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN, INVALID_CALLBACK_URL, INVALID_ERROR_CALLBACK_URL.
As application/json.
404 error
Not found (better-auth JSON). Codes: PROVIDER_NOT_FOUND (always, in this deployment), ID_TOKEN_NOT_SUPPORTED.
As application/json.
409 error
Conflict (better-auth JSON). Code: SOCIAL_ACCOUNT_ALREADY_LINKED (provider configured only).
As application/json.
417 error
Expectation failed (better-auth JSON). Code: LINKING_FAILED (provider configured only).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/unlink-account
Remove a linked sign-in method
Credentials: App session. Operation id authUnlinkAccount.
Session only, and the session must be fresh (under 24 hours old). The last remaining method can never be removed, and in this deployment every person has exactly one (credential), so the answer is 400 FAILED_TO_UNLINK_LAST_ACCOUNT.
Request body
Required; application/json.
accountIdstringrequired- The linked account's
idfrom/v2/auth/list-accounts.
Responses
200 success
Removed (not reachable while a person has one method).
As application/json.
statusbooleanalways present- Always true on success.
400 error
Bad request (better-auth JSON). Codes: FAILED_TO_UNLINK_LAST_ACCOUNT (always, here), ACCOUNT_NOT_FOUND, VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: SESSION_NOT_FRESH (session over 24 hours old), INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/get-access-token
A social provider's access token for the person. Not usable in this deployment
Credentials: App session. Operation id authGetAccessToken.
Session only. Returns (refreshing if near expiry) the OAuth access token stored for a linked social account. With no social provider configured the only linked account is credential, which answers 400 PROVIDER_NOT_SUPPORTED. The body selects the account by accountId or by useAccountCookie: true (the account cookie is not enabled here, so that form answers ACCOUNT_NOT_FOUND); userId is ignored for an HTTP caller (the session's user always wins). No other properties are allowed.
Request body
Required; application/json.
accountIdstring- The linked account's
id. Required unlessuseAccountCookieis sent. useAccountCookietrue- Select the account from better-auth's account cookie instead of
accountId(not enabled here). userIdstring- Ignored over HTTP.
Responses
200 success
Not reachable in this deployment.
As application/json.
accessTokenstring- The provider access token.
accessTokenExpiresAtstring (date-time) or null- When it expires, if known.
scopesarray of string- Granted scopes.
idTokenstring- The provider ID token, if stored.
400 error
Bad request (better-auth JSON). Codes: PROVIDER_NOT_SUPPORTED (always for credential), ACCOUNT_NOT_FOUND (unknown accountId, or the cookie form), FAILED_TO_GET_ACCESS_TOKEN, VALIDATION_ERROR (neither selector, or an extra property).
As application/json.
401 error
Unauthorized: no live session. Empty body.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/refresh-token
Refresh a social provider's tokens for the person. Not usable in this deployment
Credentials: App session. Operation id authRefreshToken.
Session only. Uses the stored OAuth refresh token of a linked social account to mint new provider tokens. Selection works as for /v2/auth/get-access-token. With no social provider configured, the credential account answers 400 PROVIDER_NOT_SUPPORTED. These are the provider's tokens, not Taigan session or API-key tokens: a Taigan session is extended by use, and API keys do not expire this way.
Request body
Required; application/json.
accountIdstring- The linked account's
id. Required unlessuseAccountCookieis sent. useAccountCookietrue- Select from the account cookie (not enabled here).
userIdstring- Ignored over HTTP.
Responses
200 success
Not reachable in this deployment.
As application/json.
accessTokenstring- The new provider access token.
refreshTokenstring- The provider refresh token (new, or the existing one).
accessTokenExpiresAtstring (date-time) or null- When the access token expires.
refreshTokenExpiresAtstring (date-time) or null- When the refresh token expires.
scopestring or null- Granted scopes, comma-separated.
idTokenstring or null- The provider ID token.
providerIdstring- The provider id.
accountIdstring- The linked account's
id.
400 error
Bad request (better-auth JSON). Codes: PROVIDER_NOT_SUPPORTED (always for credential), ACCOUNT_NOT_FOUND, TOKEN_REFRESH_NOT_SUPPORTED, REFRESH_TOKEN_NOT_FOUND, FAILED_TO_REFRESH_ACCESS_TOKEN, VALIDATION_ERROR.
As application/json.
401 error
Unauthorized: no live session. Empty body.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/account-info
The social provider's profile for a linked account. Not usable in this deployment
Credentials: App session. Operation id authAccountInfo.
Session only. Fetches the person's profile from the provider of a linked social account. With no social provider configured, the credential account answers 400 PROVIDER_NOT_CONFIGURED. Selection is by query: accountId, or useAccountCookie=true; no other query parameters are allowed. For the person's Taigan profile use GET /v2/account.
Parameters
accountIdquery · string- The linked account's
idfrom/v2/auth/list-accounts. Required unlessuseAccountCookieis sent. userIdquery · string- Ignored over HTTP (the session's user wins).
Responses
200 success
Not reachable in this deployment.
As application/json.
userobjectalways present- The profile as the provider reports it.
user.idstring- The provider's id for the person.
user.namestring- Name.
user.emailstring or null- Address.
user.imagestring or null- Picture URL.
user.emailVerifiedbooleanalways present- Whether the provider verified the address.
dataobjectalways present- The provider's raw profile payload.
accountobjectalways present- The linked account the profile came from.
account.idstringalways present- The linked account's id.
account.providerIdstringalways present- The provider id.
account.issuerstringalways present- The provider issuer.
account.accountIdstringalways present- The provider's id for the person.
400 error
Bad request (better-auth JSON). Codes: PROVIDER_NOT_CONFIGURED (always for credential), ACCOUNT_NOT_FOUND, ACCESS_TOKEN_NOT_FOUND, FAILED_TO_GET_ACCESS_TOKEN, PROVIDER_NOT_SUPPORTED, VALIDATION_ERROR (neither selector, or an extra query parameter).
As application/json.
401 error
Unauthorized. No live session (empty body), or (better-auth JSON) FAILED_TO_GET_USER_INFO from the provider.
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/callback/{id}
OAuth redirect target for a social provider (browser only)
Credentials: none. Operation id authOAuthCallback.
Anonymous; the provider sends the browser here after a social sign-in or link. Always answers a redirect: to the flow's callbackURL (or newUserCallbackURL) with a session cookie on success, or to the error target (errorCallbackURL, else /v2/auth/error) with ?error=<code>. No social provider is configured in this deployment, so every request ends at the error target (state_not_found without state, state_mismatch or please_restart_the_process with an unknown one). A browser uses this; an API client never needs it.
Parameters
idpath · stringrequired- The provider id (for example
google). codequery · string- The authorization code from the provider.
statequery · string- The OAuth state better-auth issued.
errorquery · string- An error the provider reported.
error_descriptionquery · string- The provider's error description.
device_idquery · string- A provider-specific device id (some providers).
userquery · string- A provider-specific user payload (Apple).
issquery · string- The issuer the provider reports (checked against the provider).
Responses
302 redirect
Redirect to the flow's destination, or to the error target with ?error=<code> (always the latter here). Error codes include invalid_callback_request, state_not_found, state_mismatch, no_code, provider_not_found, invalid_code, unable_to_get_user_info, email_not_found, account_already_linked_to_different_user, unable_to_link_account.
Locationheader- The destination.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/callback/{id}
OAuth form-post redirect target (browser only)
Credentials: none. Operation id authOAuthCallbackPost.
Anonymous; for providers that return with an HTML form POST (Apple). Merges the body and query fields and redirects to the GET form of the same URL, which does the work. Accepts application/x-www-form-urlencoded or JSON. Not configured in this deployment (see the GET form). A browser uses this; an API client never needs it.
Parameters
idpath · stringrequired- The provider id.
Request body
Optional; application/x-www-form-urlencoded.
Responses
302 redirect
Redirect to GET /v2/auth/callback/{id}?<the same fields>.
Locationheader- The GET form's URL.
400 error
Bad request (better-auth JSON). Code: VALIDATION_ERROR (a field that is not a string).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (a cookie-carrying POST from an untrusted origin).
As application/json.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/error
better-auth's error page (browser only)
Credentials: none. Operation id authErrorPage.
Anonymous; where a failed OAuth flow lands when no error URL was given. In production it redirects to the API host's /?error=<code>[&error_description=...]; elsewhere it renders a small HTML page naming the code. A browser uses this; an API client never needs it.
Parameters
errorquery · string- The error code to show (letters, digits,
_,-,'; anything else shows asUNKNOWN). error_descriptionquery · string- A description to show (HTML-escaped).
Responses
200 success
The HTML error page (not production).
As text/html.
302 redirect
Production: redirect to /?error=<code>.
Locationheader- The destination.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
GET /v2/auth/ok
Liveness of the auth handler
Credentials: none. Operation id authOk.
Anonymous. Answers {ok: true} when better-auth is mounted and answering. Touches no database. The service's own health check is GET /healthz.
Responses
200 success
The auth handler is up.
As application/json.
okbooleanalways present- Always true.
429 error
better-auth's request limiter (production): 100 per 10 seconds per IP. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/two-factor/enable
Start authenticator-app enrolment: returns the TOTP URI and backup codes
Credentials: App session. Operation id authTwoFactorEnable.
Session only; the current password is required. Generates a new TOTP secret and 10 backup codes and returns them; it does NOT arm the factor: enrolment takes effect when a code from the app is confirmed with POST /v2/auth/two-factor/verify-totp on this session (that writes the auth.mfa_enabled audit row). Calling it again replaces the secret and codes; if a factor is already live it stays live, so an authenticator set up from the old secret stops working. Only method: "totp" works here: mailed codes (otp) are not configured. Support operators must have a live factor.
Request body
Required; application/json.
passwordstringrequired- The person's current password.
method"totp" | "otp"totp(an authenticator app).otpanswers 400OTP_NOT_CONFIGUREDhere. (default "totp")issuerstring- Issuer label in the authenticator app. Defaults to
Data Tactical Group.
Responses
200 success
The secret's URI and the backup codes. Show both once; they are not retrievable later (two_factor.backup_codes_remaining on /v2/account gives only a count).
As application/json.
method"totp"always present- Always
totphere. totpURIstringalways present- An
otpauth://totp/...URI (6 digits, 30-second period) to render as a QR code. backupCodesarray of stringalways present- 10 one-time backup codes, each
xxxxx-xxxxx(letters and digits).
400 error
Bad request (better-auth JSON). Codes: INVALID_PASSWORD (missing or wrong), OTP_NOT_CONFIGURED (method: otp), VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 3 per 10 seconds per IP on /two-factor/*. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/two-factor/verify-totp
Verify an authenticator code: confirm enrolment, or answer a sign-in challenge
Credentials: App session. Operation id authTwoFactorVerifyTotp.
Two uses. (1) With a session: confirms enrolment. The first correct code arms the factor (twoFactorEnabled true), replaces the session with a new one (new cookie and token) and writes an auth.mfa_enabled audit row; a correct code on an already-armed factor keeps the session and marks it as having passed the challenge (T110: how an unchallenged DTG admin session reaches /v2/admin). (2) Without a session, with the dtg.two_factor challenge cookie that POST /v2/auth/sign-in/email set: completes the sign-in, creating the session (with all the sign-in effects) and, with trustDevice: true, a dtg.trust_device cookie that skips the challenge on this browser for 30 days. Challenge limits: 5 wrong codes on one challenge end it (the sixth attempt answers 400 TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE; sign in again), and 10 wrong codes in a row across challenges lock the factor for 15 minutes (429 ACCOUNT_TEMPORARILY_LOCKED, checked before the code, so a correct code is refused while it stands). The lockout and per-challenge cap apply only to sign-in challenges, not to enrolment with a session.
Request body
Required; application/json.
codestringrequired- The 6-digit code from the authenticator app.
trustDeviceboolean- Sign-in challenge only: trust this browser for 30 days.
Responses
200 success
Verified.
As application/json.
tokenstring or null- The session token now in the session cookie (the new session on a sign-in challenge or on a first enrolment, otherwise the current one). Null or absent only where the operation says so.
userAuthUseralways present- The better-auth user (the identity behind a Taigan account; one to one with
accounts). Timestamps are ISO 8601 strings. user.idstring (uuid)always present- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestringalways present- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)always present- The sign-in address, lower-cased.
user.emailVerifiedbooleanalways present- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)always present- When the user was created.
user.updatedAtstring (date-time)always present- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable).
400 error
Bad request (better-auth JSON). Codes: TOTP_NOT_ENABLED (no enrolment started, or a sign-in challenge for a factor never confirmed), TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE (the challenge is spent; sign in again; does not count toward the lockout), VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Codes: INVALID_CODE (wrong code; on a challenge it counts toward both limits), INVALID_TWO_FACTOR_COOKIE (no session and no live challenge cookie: it expires after 10 minutes).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN (cookie-carrying POST from an untrusted origin).
As application/json.
429 error
Too many requests, with Retry-After. Either the second-factor lock, code ACCOUNT_TEMPORARILY_LOCKED (body AuthAccountLocked), or better-auth's per-IP limiter in production (3 per 10 seconds on /two-factor/*; body {message}, no code, plus X-Retry-After).
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
500 error
Server error (better-auth JSON). Codes: FAILED_TO_CREATE_SESSION, FAILED_TO_INVALIDATE_TWO_FACTOR_CHALLENGE.
As application/json.
POST /v2/auth/two-factor/verify-backup-code
Answer a sign-in challenge (or check a code) with a one-time backup code
Credentials: App session. Operation id authTwoFactorVerifyBackupCode.
Without a session, with the dtg.two_factor challenge cookie: completes the sign-in like /two-factor/verify-totp (same per-challenge cap of 5 and the same 10-failure lock). With a session: checks the code. Either way a correct code is consumed and cannot be used again. With disableSession: true the code is consumed but no session is created or changed (on a challenge, token is then absent and the challenge stays open). A backup code never arms or disarms a factor, so no audit row is written.
Request body
Required; application/json.
codestringrequired- One backup code,
xxxxx-xxxxx. disableSessionboolean- True to consume the code without creating a session or setting a cookie.
trustDeviceboolean- Sign-in challenge only: trust this browser for 30 days.
Responses
200 success
Accepted; the code is used up.
As application/json.
tokenstring or null- The session token now in the session cookie (the new session on a sign-in challenge or on a first enrolment, otherwise the current one). Null or absent only where the operation says so.
userAuthUseralways present- The better-auth user (the identity behind a Taigan account; one to one with
accounts). Timestamps are ISO 8601 strings. user.idstring (uuid)always present- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestringalways present- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)always present- The sign-in address, lower-cased.
user.emailVerifiedbooleanalways present- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)always present- When the user was created.
user.updatedAtstring (date-time)always present- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable).
400 error
Bad request (better-auth JSON). Codes: BACKUP_CODES_NOT_ENABLED (no factor enrolled), TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE (challenge spent; sign in again), VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Codes: INVALID_BACKUP_CODE (wrong or already used), INVALID_TWO_FACTOR_COOKIE (no session and no live challenge).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
409 error
Conflict (better-auth JSON, no code): message Failed to verify backup code. Please try again. (two requests used codes at the same instant; retry).
As application/json.
429 error
Too many requests, with Retry-After. Either ACCOUNT_TEMPORARILY_LOCKED (body AuthAccountLocked; sign-in challenges only) or better-auth's per-IP limiter (3 per 10 seconds on /two-factor/*; body {message}, no code).
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
500 error
Server error (better-auth JSON). Codes: FAILED_TO_CREATE_SESSION, FAILED_TO_INVALIDATE_TWO_FACTOR_CHALLENGE.
As application/json.
POST /v2/auth/two-factor/send-otp
Mail a one-time second-factor code. Not configured in this deployment
Credentials: App session. Operation id authTwoFactorSendOtp.
Mailed (OTP) codes are not configured in Taigan (the plugin has no sendOTP), so every request answers 400 OTP_NOT_CONFIGURED before anything else is checked. The authenticator app (verify-totp) and backup codes are the factors.
Request body
Optional; application/json.
trustDeviceboolean- Unused here.
Responses
400 error
Bad request (better-auth JSON). Codes: OTP_NOT_CONFIGURED (always), VALIDATION_ERROR.
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 3 per 10 seconds on /two-factor/*. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/two-factor/verify-otp
Verify a mailed second-factor code. Not usable in this deployment
Credentials: App session. Operation id authTwoFactorVerifyOtp.
Mailed codes are not configured (see /two-factor/send-otp), so no code can ever be pending: with a session or a live challenge cookie the answer is 400 OTP_HAS_EXPIRED, except that on a sign-in challenge a standing second-factor lock is reported first (429). Without either, 401 INVALID_TWO_FACTOR_COOKIE.
Request body
Required; application/json.
codestringrequired- The mailed code.
trustDeviceboolean- Sign-in challenge only: trust this browser for 30 days.
Responses
200 success
Never answered in this deployment.
As application/json.
tokenstring or null- The session token now in the session cookie (the new session on a sign-in challenge or on a first enrolment, otherwise the current one). Null or absent only where the operation says so.
userAuthUseralways present- The better-auth user (the identity behind a Taigan account; one to one with
accounts). Timestamps are ISO 8601 strings. user.idstring (uuid)always present- The auth user's id (
auth_users.id). Not the Taigan account id that/v2/accountreports. user.namestringalways present- The name given at sign-up or set through
POST /v2/auth/update-user. Mirrored into the account'sdisplay_name. user.emailstring (email)always present- The sign-in address, lower-cased.
user.emailVerifiedbooleanalways present- Whether the address has been proved by its verification link. Sign-in is refused until it is true.
user.imagestring or null- A profile image URL. Stored if supplied; Taigan and Kelpie never show it.
user.createdAtstring (date-time)always present- When the user was created.
user.updatedAtstring (date-time)always present- When the user was last changed.
user.orgNamestring or null- The organization name typed on the sign-up form, used once to name the person's own organization. Never read again; the organization's real name is on
/v2/account. user.marketingOptInboolean or null- The person's marketing consent, kept equal to the opposite of the account's
marketing_opt_out: set from the sign-up form's box, then byupdate-user,PATCH /v2/account/profileand the unsubscribe link alike. user.twoFactorEnabledboolean or null- Whether a second factor is live for this user (set when an enrolment is confirmed, cleared by
/two-factor/disable).
400 error
Bad request (better-auth JSON). Codes: OTP_HAS_EXPIRED (always, with a session or challenge), TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE, VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Codes: INVALID_TWO_FACTOR_COOKIE (no session and no live challenge), INVALID_CODE.
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
Too many requests, with Retry-After. Either ACCOUNT_TEMPORARILY_LOCKED (body AuthAccountLocked; sign-in challenges only) or better-auth's per-IP limiter (3 per 10 seconds on /two-factor/*; body {message}, no code).
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/two-factor/get-totp-uri
Show the authenticator URI again (current password required)
Credentials: App session. Operation id authTwoFactorGetTotpUri.
Session only. Returns the otpauth:// URI of the person's current TOTP secret (to add the factor to another device). Changes nothing.
Request body
Required; application/json.
passwordstringrequired- The person's current password.
Responses
200 success
The URI.
As application/json.
totpURIstringalways present- An
otpauth://totp/...URI (6 digits, 30-second period, issuerData Tactical Group).
400 error
Bad request (better-auth JSON). Codes: TOTP_NOT_ENABLED (no enrolment started), INVALID_PASSWORD (missing or wrong), VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 3 per 10 seconds on /two-factor/*. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/two-factor/generate-backup-codes
Replace the backup codes (current password required)
Credentials: App session. Operation id authTwoFactorGenerateBackupCodes.
Session only, with a live second factor. Generates 10 new backup codes and invalidates every old one. Show them once: they are not retrievable later (/v2/account gives only two_factor.backup_codes_remaining).
Request body
Required; application/json.
passwordstringrequired- The person's current password.
Responses
200 success
The new codes.
As application/json.
statustruealways present- Always true.
backupCodesarray of stringalways present- 10 one-time codes, each
xxxxx-xxxxx.
400 error
Bad request (better-auth JSON). Codes: TWO_FACTOR_NOT_ENABLED (no live factor), INVALID_PASSWORD (missing or wrong), VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 3 per 10 seconds on /two-factor/*. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
POST /v2/auth/two-factor/disable
Turn the second factor off (current password required)
Credentials: App session. Operation id authTwoFactorDisable.
Session only. Deletes the factor (secret and backup codes), clears twoFactorEnabled, replaces the session with a new one (new cookie), forgets a trusted device, and writes an auth.mfa_disabled audit row when a live factor was turned off. An operator whose factor is off is refused operator access until they enrol again.
Request body
Required; application/json.
passwordstringrequired- The person's current password.
Responses
200 success
Turned off.
As application/json.
statusbooleanalways present- Always true on success.
400 error
Bad request (better-auth JSON). Codes: INVALID_PASSWORD (missing or wrong), VALIDATION_ERROR.
As application/json.
401 error
Unauthorized (better-auth JSON). Code: UNAUTHORIZED (no live session).
As application/json.
403 error
Forbidden (better-auth JSON). Codes: INVALID_ORIGIN / MISSING_OR_NULL_ORIGIN.
As application/json.
429 error
better-auth's request limiter (production): 3 per 10 seconds on /two-factor/*. Body {message}, no code.
As application/json.
Retry-Afterheader- Seconds to wait before retrying.
Generated from taigan/openapi.yaml at catahoula 77925fab712f(API document version 2.0).