API reference

Sign-in

Registration, sign-in, two-factor and the hand-off between the site and a workspace.

  1. POST/v1/auth/2fa/setup
  2. POST/v1/auth/2fa/setup/confirm
  3. POST/v1/auth/2fa/verify
  4. POST/v1/auth/account-handoff
  5. POST/v1/auth/account-handoff/exchange
  6. POST/v1/auth/account-login
  7. POST/v1/auth/email/resend
  8. POST/v1/auth/email/verify
  9. POST/v1/auth/google
  10. POST/v1/auth/handoff
  11. POST/v1/auth/handoff/exchange
  12. POST/v1/auth/invite/accept
  13. POST/v1/auth/login
  14. POST/v1/auth/logout
  15. POST/v1/auth/password-reset/confirm
  16. POST/v1/auth/password-reset/request
  17. POST/v1/auth/refresh
  18. POST/v1/auth/register
POST/v1/auth/2fa/setup

Start mandatory 2FA enrolment during sign-in

For an account that answered twoFactorSetupRequired. Redeems the challengeToken for a fresh secret + QR code. Nothing is live until /auth/2fa/setup/confirm.

Needs: Nothing. This route is public.

Request body · TwoFactorSetupChallengeDto
FieldTypeWhat it is
challengeToken requiredstringFrom the twoFactorSetupRequired response.
Responses
StatusBodyWhen
200TwoFactorSetupResponseDto
401—Invalid or expired challenge
200 body · TwoFactorSetupResponseDto
FieldTypeWhat it is
secret requiredstringFor manual entry, when the authenticator app can't scan a code.
otpauthUrl requiredstring
qrCodeDataUrl requiredstringA data:image/png;base64,... QR code of otpauthUrl — draw it straight into an <img>.
POST/v1/auth/2fa/setup/confirm

Finish mandatory 2FA enrolment and sign in

Proves the authenticator app has the secret, turns 2FA on, and returns the session the original register/login call would have — plus backupCodes, which are shown once and never again.

Needs: Nothing. This route is public.

Request body · TwoFactorVerifyDto
FieldTypeWhat it is
challengeToken requiredstringFrom the twoFactorRequired response.
code requiredstringA 6-digit authenticator code, or one of the 10-character backup codes.
Responses
StatusBodyWhen
200(AccountSessionResponseDto | AuthSessionResponseDto) & objectThe session (account token, or a full workspace session when the sign-in named one) with backupCodes: string[] merged in.
401—Invalid/expired challenge, or a wrong code
200 body · either AccountSessionResponseDto
FieldTypeWhat it is
accountToken requiredstringIdentity only, no org. Use it for /v1/billing/demo and /v1/billing/checkout.
user requiredUserSummaryDto
user.id requiredstring
user.email requiredstring
backupCodes requiredstring[]
200 body · either AuthSessionResponseDto
FieldTypeWhat it is
accessToken requiredstring
refreshToken requiredstring
user requiredUserSummaryDto
user.id requiredstring
user.email requiredstring
organization requiredOrganizationSummaryDtoThe active organization: the newly-created org on register, or the workspace signed in to on login (the subdomain's org). Always the org the token is scoped to.
organization.id requiredstring
organization.name requiredstring
organization.slug requiredstring
backupCodes requiredstring[]
POST/v1/auth/2fa/verify

Complete a 2FA-gated sign-in

Redeems the challengeToken from account-login/login/google plus a TOTP or backup code, and returns exactly what that original call would have.

Needs: Nothing. This route is public.

Request body · TwoFactorVerifyDto
FieldTypeWhat it is
challengeToken requiredstringFrom the twoFactorRequired response.
code requiredstringA 6-digit authenticator code, or one of the 10-character backup codes.
Responses
StatusBodyWhen
200AccountSessionResponseDto | AuthSessionResponseDto
401—Invalid/expired challenge, or invalid/expired code
200 body · either AccountSessionResponseDto
FieldTypeWhat it is
accountToken requiredstringIdentity only, no org. Use it for /v1/billing/demo and /v1/billing/checkout.
user requiredUserSummaryDto
user.id requiredstring
user.email requiredstring
200 body · either AuthSessionResponseDto
FieldTypeWhat it is
accessToken requiredstring
refreshToken requiredstring
user requiredUserSummaryDto
user.id requiredstring
user.email requiredstring
organization requiredOrganizationSummaryDtoThe active organization: the newly-created org on register, or the workspace signed in to on login (the subdomain's org). Always the org the token is scoped to.
organization.id requiredstring
organization.name requiredstring
organization.slug requiredstring
POST/v1/auth/account-handoff

Create a one-time code for going from a workspace to the customer area

The reverse of POST /v1/auth/handoff. Billing lives on the marketing origin, not in the panel, and a session cookie cannot cross between the two — so someone clicking "choose a plan" in the panel carries a short-lived single-use code instead of retyping their password. The workspace comes from the caller’s own token, never from the body.

Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.

Responses
StatusBodyWhen
200HandoffDto
200 body · HandoffDto
FieldTypeWhat it is
code requiredstringSingle-use, expires in about a minute.
url requiredstringWhere to redeem it — the workspace's own origin.
POST/v1/auth/account-handoff/exchange

Redeem a one-time code for an account session

Yields identity only — the same token account-login returns. Arriving from a workspace never produces a stronger credential than signing in here would.

Needs: Nothing. This route is public.

Request body · RedeemHandoffDto
FieldTypeWhat it is
code requiredstringThe one-time code from the sign-in host.
Responses
StatusBodyWhen
200AccountHandoffSessionDto
200 body · AccountHandoffSessionDto
FieldTypeWhat it is
accountToken requiredstring
user requiredUserSummaryDto
user.id requiredstring
user.email requiredstring
organization requiredOrganizationSummaryDto | null
organization.id requiredstring
organization.name requiredstring
organization.slug requiredstring
POST/v1/auth/account-login

Verify a password without entering a workspace

Used by the shared sign-in host and the customer area, where nobody has chosen a workspace yet. Always answers with a two-factor step, never a token directly.

Needs: Nothing. This route is public.

Request body · AccountLoginDto
FieldTypeWhat it is
email requiredstring (email)The account email.
password requiredstring
Responses
StatusBodyWhen
200TwoFactorChallengeResponseDto | TwoFactorSetupRequiredResponseDtoEvery account must have 2FA on — there is no opt out. An account that already has it enrolled gets a twoFactorRequired challenge (redeem at POST /auth/2fa/verify); one that does not gets twoFactorSetupRequired instead (redeem at POST /auth/2fa/setup, then /auth/2fa/setup/confirm). Neither is optional to handle: no account can reach a real session without going through one of them.
200 body · either TwoFactorChallengeResponseDto
FieldTypeWhat it is
twoFactorRequired requiredboolean
challengeToken requiredstringTrade this, plus a code, at POST /v1/auth/2fa/verify. Expires in ~10 minutes.
200 body · either TwoFactorSetupRequiredResponseDto
FieldTypeWhat it is
twoFactorSetupRequired requiredboolean
challengeToken requiredstring
POST/v1/auth/email/resend

Send another confirmation email

ALWAYS returns 204, for the same enumeration reason as the reset request. An unknown address, an already-confirmed one and a pending one are indistinguishable from the response.

Needs: Nothing. This route is public.

Request body · ResendVerificationDto
FieldTypeWhat it is
email requiredstring (email)The account email. Always answered with 204, like the reset request.
Responses
StatusBodyWhen
204—Accepted. Says nothing about whether the address exists.
POST/v1/auth/email/verify

Confirm an email address

Marks the address as confirmed, using the token from the emailed link.

Needs: Nothing. This route is public.

Request body · VerifyEmailDto
FieldTypeWhat it is
token requiredstringThe token from the emailed link.
Responses
StatusBodyWhen
200EmailVerifiedResponseDto
400—The link is invalid, expired, or already used.
200 body · EmailVerifiedResponseDto
FieldTypeWhat it is
email requiredstring
verifiedAt requiredstringISO timestamp of when the address was first confirmed.
POST/v1/auth/google

Sign in with Google

Takes the ID token from Google Identity Services and returns an ACCOUNT token — identity, no workspace — exactly as register does. A Google identity says who you are, not which workspace you meant.

Needs: Nothing. This route is public.

Request body · GoogleSignInDto
FieldTypeWhat it is
idToken requiredstringThe ID token returned by Google Identity Services. Verified server-side against Google's published keys — a decoded token proves nothing.
allowSignupbooleanMay an unknown Google address create an account? True on the marketing site, where Google is a sign-up route. The panel sign-in host sends false, so an address with no account is told so instead of silently getting one.
orgSlugstringName a workspace to receive a full session for it, as the panel does when signing in on a workspace subdomain. Omit it — as the marketing site does — to receive an account token with no workspace.
Responses
StatusBodyWhen
200AuthSessionResponseDto | AccountSessionResponseDtoA full session when orgSlug was given, otherwise an account token. Google sign-in is never itself gated behind this account's 2FA — see AuthService.
401—The Google token could not be verified
200 body · either AuthSessionResponseDto
FieldTypeWhat it is
accessToken requiredstring
refreshToken requiredstring
user requiredUserSummaryDto
user.id requiredstring
user.email requiredstring
organization requiredOrganizationSummaryDtoThe active organization: the newly-created org on register, or the workspace signed in to on login (the subdomain's org). Always the org the token is scoped to.
organization.id requiredstring
organization.name requiredstring
organization.slug requiredstring
200 body · either AccountSessionResponseDto
FieldTypeWhat it is
accountToken requiredstringIdentity only, no org. Use it for /v1/billing/demo and /v1/billing/checkout.
user requiredUserSummaryDto
user.id requiredstring
user.email requiredstring
POST/v1/auth/handoff

Create a one-time code for entering a workspace

The session belongs to the workspace origin, and a cookie cannot cross between two sites — so a short-lived single-use code travels in the URL instead. It is worth nothing after about a minute, or after one use.

Needs: A signed-in panel session, sent as Authorization: Bearer <access token>.

Request body · IssueHandoffDto
FieldTypeWhat it is
orgSlug requiredstringThe workspace being entered.
Responses
StatusBodyWhen
200HandoffDto
200 body · HandoffDto
FieldTypeWhat it is
code requiredstringSingle-use, expires in about a minute.
url requiredstringWhere to redeem it — the workspace's own origin.
POST/v1/auth/handoff/exchange

Redeem a one-time code for a session

Checks access again rather than trusting the code: a code created a minute ago cannot know whether the workspace was suspended since.

Needs: Nothing. This route is public.

Request body · RedeemHandoffDto
FieldTypeWhat it is
code requiredstringThe one-time code from the sign-in host.
Responses
StatusBodyWhen
200AuthSessionResponseDto
200 body · AuthSessionResponseDto
FieldTypeWhat it is
accessToken requiredstring
refreshToken requiredstring
user requiredUserSummaryDto
user.id requiredstring
user.email requiredstring
organization requiredOrganizationSummaryDtoThe active organization: the newly-created org on register, or the workspace signed in to on login (the subdomain's org). Always the org the token is scoped to.
organization.id requiredstring
organization.name requiredstring
organization.slug requiredstring
POST/v1/auth/invite/accept

Redeem a team invitation

An email that already has an account joins directly. An email with none needs name and password to create one; without them the answer is a 400, which a client can show as a "create your account" step before sending again.

Needs: Nothing. This route is public.

Request body · AcceptInviteDto
FieldTypeWhat it is
token requiredstringThe <id>.<secret> token from the invite link.
namestringOnly used when accepting the invite creates a brand-new account — an invite for an email that already has one ignores both fields entirely.
passwordstringRequired only when no account exists yet for the invited email.
Responses
StatusBodyWhen
200TwoFactorChallengeResponseDto | TwoFactorSetupRequiredResponseDtoThe invitation is claimed either way — but the session behind it still comes through the two-factor step. Every account must have 2FA on — there is no opt out. An account that already has it enrolled gets a twoFactorRequired challenge (redeem at POST /auth/2fa/verify); one that does not gets twoFactorSetupRequired instead (redeem at POST /auth/2fa/setup, then /auth/2fa/setup/confirm). Neither is optional to handle: no account can reach a real session without going through one of them.
200 body · either TwoFactorChallengeResponseDto
FieldTypeWhat it is
twoFactorRequired requiredboolean
challengeToken requiredstringTrade this, plus a code, at POST /v1/auth/2fa/verify. Expires in ~10 minutes.
200 body · either TwoFactorSetupRequiredResponseDto
FieldTypeWhat it is
twoFactorSetupRequired requiredboolean
challengeToken requiredstring
POST/v1/auth/login

Authenticate with email + password against one workspace

Always answers with a two-factor step, never a session directly.

Needs: Nothing. This route is public.

Request body · LoginDto
FieldTypeWhat it is
email requiredstring (email)
password requiredstring
orgSlug requiredstringThe workspace subdomain slug to sign in to (server resolves org + role).
Responses
StatusBodyWhen
200TwoFactorChallengeResponseDto | TwoFactorSetupRequiredResponseDtoEvery account must have 2FA on — there is no opt out. An account that already has it enrolled gets a twoFactorRequired challenge (redeem at POST /auth/2fa/verify); one that does not gets twoFactorSetupRequired instead (redeem at POST /auth/2fa/setup, then /auth/2fa/setup/confirm). Neither is optional to handle: no account can reach a real session without going through one of them.
401—Invalid email or password
200 body · either TwoFactorChallengeResponseDto
FieldTypeWhat it is
twoFactorRequired requiredboolean
challengeToken requiredstringTrade this, plus a code, at POST /v1/auth/2fa/verify. Expires in ~10 minutes.
200 body · either TwoFactorSetupRequiredResponseDto
FieldTypeWhat it is
twoFactorSetupRequired requiredboolean
challengeToken requiredstring
POST/v1/auth/logout

Revoke a refresh token

Needs: Nothing. This route is public.

Request body · LogoutDto
FieldTypeWhat it is
refreshToken requiredstring
Responses
StatusBodyWhen
204—
POST/v1/auth/password-reset/confirm

Set a new password using a reset link

Single-use and short-lived. On success the new password is stored, EVERY refresh token the user holds is revoked, and a "your password was changed" notice is sent. Expired, already-used and unknown tokens all return the same 400 with the same message.

Needs: Nothing. This route is public.

Request body · ConfirmPasswordResetDto
FieldTypeWhat it is
token requiredstringThe token from the emailed link.
password requiredstringThe new password: 8 to 128 characters, the same rule as at sign-up.
Responses
StatusBodyWhen
204—Password changed; all sessions revoked.
400—The link is invalid, expired, or already used.
POST/v1/auth/password-reset/request

Ask for a password-reset link

ALWAYS returns 204, whether or not the address has an account. Any other behaviour — a 404, a different body, a different error — would let anyone test an email list against our user table. The client must therefore show "if that address has an account, we have sent a link" and nothing more specific.

Needs: Nothing. This route is public.

Request body · RequestPasswordResetDto
FieldTypeWhat it is
email requiredstring (email)The account email. The response is 204 whether or not it exists — see the endpoint description.
Responses
StatusBodyWhen
204—Accepted. Says nothing about whether the address exists.
POST/v1/auth/refresh

Rotate a refresh token for a new access + refresh pair

Needs: Nothing. This route is public.

Request body · RefreshDto
FieldTypeWhat it is
refreshToken requiredstring
Responses
StatusBodyWhen
200RefreshResponseDto
401—Invalid, expired, or reused refresh token
200 body · RefreshResponseDto
FieldTypeWhat it is
accessToken requiredstring
refreshToken requiredstring
POST/v1/auth/register

Create an account

Creates the user only — no workspace. A workspace costs either a demo request (POST /v1/billing/demo) or a payment (POST /v1/billing/checkout); the returned account token authenticates both.

Needs: Nothing. This route is public.

Request body · RegisterDto
FieldTypeWhat it is
email requiredstring (email)
password requiredstringThe password: 8 to 128 characters.
namestringThe person's own name. Optional, but when given it must be at least two characters and not only spaces.
organizationNamestringThe workspace's name. Optional: a name is made from the email address when it is left out. When given it must not be only spaces.
Responses
StatusBodyWhen
201TwoFactorSetupRequiredResponseDtoNever a token: a brand-new account has no 2FA yet and enrolment is mandatory, so registration always answers with twoFactorSetupRequired. The account token arrives from POST /auth/2fa/setup/confirm once enrolment finishes.
409—Email already registered
201 body · TwoFactorSetupRequiredResponseDto
FieldTypeWhat it is
twoFactorSetupRequired requiredboolean
challengeToken requiredstring

Next groupAccount