switching clears the token & responses
No token
33

Signing in (operator authentication)

Operators authenticate via /v1/admin/auth/* over the internal-only ingress, with MFA mandatory (TOTP — the tenant-facing phone-OTP method does not apply). The operator access token is minted here (aud: iam-platform); the gateway then verifies it to mint the x-gateway-context every other /v1/admin/* route consumes. Device trust does NOT apply to operators — by decision, not omission: a mandatory second factor on every login is the control, so sign-in is identical on a familiar and an unfamiliar machine whatever DEVICE_TRUST_ENABLED is set to.

  1. POST /v1/admin/auth/accept-setup → set password + begin MFA enrolment (provisioningUri)
  2. POST /v1/admin/auth/mfa/enroll/confirm → recovery codes + activate operator
  3. POST /v1/admin/auth/login → mandatory MFA challenge { mfaRequired, mfaToken }
  4. POST /v1/admin/auth/mfa/verify → operator access token (aud: iam-platform) + refresh
  5. POST /v1/admin/auth/refresh → rotate refresh → fresh operator access token
  6. POST /v1/admin/auth/logout → revoke the current operator session
POST/v1/admin/auth/accept-setuppublicAccept setup (one-time)

Pre-auth (the setup token authorises the call; no bearer). Consumes the out-of-band setup token from operator-create (scenario 34), sets the password, and begins MFA enrolment → returns a TOTP provisioningUri. Account is not active until enrolment is confirmed.

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎Field guide — what each value means & where it comes from2 fields

Pre-auth, internal-only: the single-use out-of-band setup token authorises the call, so no Authorization or x-gateway-context header is needed. The strict JSON body takes only setupToken and password; it consumes the token, sets the operator password, and begins TOTP MFA enrolment (returns a provisioningUri to render as a QR).

setupTokenbodystringrequiredout-of-band
You must OBTAIN this; you do not type it freely. It is the single-use, out-of-band operator setup token minted when the operator account was created (POST /v1/admin/operators, §3.58) and delivered via a side channel (e.g. setup email/link). 1-512 chars. It authorises this pre-auth call and is consumed on success.
passwordbodystringrequiredyou choose
The new operator's chosen password, freely typed by the user. Must be >=12 and <=1024 chars; it is argon2id-hashed server-side and set as the operator's password.
POST https://api.kerja.team/v1/admin/auth/accept-setup
↩︎Response guide — what comes back & what each value means2 fields

200 OK with a { data } envelope; response carries Cache-Control: no-store (no Set-Cookie). Body confirms MFA enrolment has begun: { data: { mfaEnrollmentRequired: true, provisioningUri } } — the setup token is consumed and the account remains inactive until enrolment is confirmed.

data.mfaEnrollmentRequiredbooleanalways
Signals that the operator must now complete TOTP MFA enrolment before the account becomes active. The client should render the QR/provisioning step and proceed to confirm enrolment (§3.52).
  • trueMFA enrolment has begun and must be confirmed; the account is not yet active. This is the only value returned on success.
data.provisioningUristringalways
An otpauth://totp/... URI containing the unconfirmed TOTP secret and issuer label. Render it as a QR code (or show the secret) for the operator's authenticator app, then submit a current code to POST /v1/admin/auth/mfa/enroll/confirm.
POST/v1/admin/auth/mfa/enroll/confirmpublicConfirm MFA enrolment (one-time)

Pre-auth. Confirms the TOTP code → returns one-time recovery codes and activates the operator account.

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎Field guide — what each value means & where it comes from2 fields

Pre-auth setup-flow endpoint (no Authorization / x-gateway-context). Confirms an operator's TOTP enrolment begun at POST /v1/admin/auth/accept-setup (§3.51): send the same setup token plus a current TOTP code; on success it returns one-time recovery codes and activates the operator. No required Idempotency-Key.

setupTokenbodystringrequiredout-of-band
The same operator setup token issued/used by the enrol step (POST /v1/admin/auth/accept-setup, §3.51). You must obtain it from that setup flow side channel and paste it here, not type it freely. 1-512 chars. Identifies and authorizes the enrolment being confirmed; it is consumed on success.
codebodystringrequiredauthenticator
The current TOTP code generated by the operator's authenticator app for the secret being enrolled. The user reads it from their authenticator, not picks it freely. 1-32 chars. Must be valid for the enrolment to confirm; a wrong code yields 401 UNAUTHORIZED.
POST https://api.kerja.team/v1/admin/auth/mfa/enroll/confirm
↩︎Response guide — what comes back & what each value means2 fields

200 OK, { data } envelope, Cache-Control: no-store (recovery codes are sensitive and returned only once). No Set-Cookie. Body confirms TOTP enrolment, returns one-time recovery codes, and activates the operator account.

data.statusenumalways
New operator account status after successful enrolment confirmation. Signals the account is now usable.
  • activeOperator account is activated; TOTP enrolment confirmed and setup token consumed.
data.recoveryCodes[]arrayalways
One-time recovery codes (e.g. "a1b2-c3d4"), hashed at rest and returned ONLY in this response — never retrievable again. The client must display/store them securely for the operator; each code is single-use for MFA if the authenticator is lost.
POST/v1/admin/auth/loginpublicOperator login (stage one)

Pre-auth. MFA is mandatory, so this ALWAYS returns { mfaRequired, mfaToken } — never a token directly. Copy mfaToken into the next call.

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎Field guide — what each value means & where it comes from2 fields

Operator password login, stage one — pre-auth, internal-only, throttled, never cached (no Authorization needed). Body is .strict() (unknown fields rejected). Because MFA is mandatory for every operator a correct password ALWAYS returns an MFA challenge { mfaRequired, mfaToken } — never a token — so copy mfaToken into POST /v1/admin/auth/mfa/verify to finish. No Idempotency-Key required.

emailbodystringrequiredyou choose
The operator account email; you type it. Must be a valid email, at most 320 chars. Combined with password to authenticate factor one. Failures return a uniform INVALID_CREDENTIALS that never reveals which factor was wrong.
passwordbodystringrequiredyou choose
The operator account password; you type it. 1–1024 chars. On success the response is only an MFA challenge (mfaToken), not an access token.
POST https://api.kerja.team/v1/admin/auth/login
↩︎Response guide — what comes back & what each value means2 fields

200 OK with the standard { data } envelope; Cache-Control: no-store (no Set-Cookie at this stage). Because operator MFA is mandatory, this stage-one password login ALWAYS returns an MFA challenge { mfaRequired, mfaToken } and NEVER an access/refresh token — the real session is minted only by §3.54 POST /v1/admin/auth/mfa/verify.

data.mfaRequiredbooleanalways
Always true — signals the client that stage two (MFA verify) is required before any token is issued. The tester should branch to the MFA verify call.
  • trueMFA challenge is mandatory; no access token has been issued. Proceed to POST /v1/admin/auth/mfa/verify with the mfaToken plus the TOTP/recovery code.
data.mfaTokenJWTalways
Single-purpose, short-TTL (~5 min) JWT identifying this in-progress login — NOT an access token. Capture it and pass it as the mfaToken field of the next call (POST /v1/admin/auth/mfa/verify) to complete login.
POST/v1/admin/auth/mfa/verifypublicOperator MFA verify (stage two)

Pre-auth. Mints the operator access token (aud: iam-platform) + rotating refresh. data.accessToken is auto-captured as the active bearer for the other /v1/admin/* calls. Operator roles are NOT in the token.

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎Field guide — what each value means & where it comes from2 fields

Stage-two operator MFA verification that completes login and mints the operator access + refresh tokens. Pre-auth and internal-only: no Authorization or x-gateway-context is sent yet; the mfaToken from §3.53 stand-in is the credential. No Idempotency-Key required.

mfaTokenbodystringrequiredfrom a response
Do not type this yourself: copy it from the prior operator login response (the mfaToken JWT returned by POST /v1/admin/auth/login, §3.53). It identifies the pending operator MFA challenge this verification completes. 1-4096 chars.
codebodystringrequiredauthenticator
The current 6-digit TOTP code from the operator's MFA authenticator app, OR a single-use recovery code. You obtain it from the authenticator, not from any API response. A correct recovery code is consumed (single-use). 1-32 chars.
POST https://api.kerja.team/v1/admin/auth/mfa/verify
↩︎Response guide — what comes back & what each value means8 fields

200 OK, { data } envelope, Cache-Control: no-store (refresh token returned in the body, not as a Set-Cookie). Stage-two operator MFA: mints the operator access token (aud: iam-platform) plus a rotating refresh token and returns the operator profile.

data.accessTokenJWTalways
The freshly minted operator access token (token_use: access, aud: iam-platform, sub = operator id, sid = operator session). Auto-captured by the tester as the active bearer token for subsequent admin/operator calls. Operator roles are NOT in this token.
data.tokenTypeenumalways
Token scheme to use in the Authorization header.
  • BearerSend the access token as 'Authorization: Bearer <accessToken>'.
data.expiresInintegeralways
Operator access-token TTL in seconds (short-lived; e.g. 600). Client uses it to know when to refresh before expiry.
data.refreshTokenstringalways
Rotating operator refresh token (opaque). Returned in the body (no-store); feed into POST /v1/admin/auth/refresh to rotate and obtain a new access token.
data.operatorobjectalways
The authenticated operator's profile summary.
data.operator.idUUIDalways
Operator identifier (the access token's sub). Identifies the logged-in operator.
data.operator.emailstringalways
Operator's email address (e.g. grace@fam.local).
data.operator.displayNamestringalways
Operator's human-readable display name for UI presentation.
POST/v1/admin/auth/refreshpublicOperator refresh

Pre-auth (bears the refresh token). Rotates the refresh and mints a fresh operator access token (auto-captured). Reuse of a used token revokes the operator session.

Request body (JSON)
ℹ︎Field guide — what each value means & where it comes from1 field

Rotates the operator refresh token and mints a fresh operator access token. Pre-auth: the refresh token in the body IS the credential, so no Authorization / x-gateway-context is needed. Single body field; reuse of an already-used token revokes the operator session.

refreshTokenbodystringrequiredfrom a response
The operator refresh token. Obtain it from a prior operator-auth response in the tester (the refreshToken field returned by operator login or by a previous call to this refresh endpoint) and paste it here. Must be 1-4096 chars. It is rotated on success; replaying a used token revokes the entire operator session.
POST https://api.kerja.team/v1/admin/auth/refresh
↩︎Response guide — what comes back & what each value means4 fields

200 OK with the standard { data } envelope; body carries a freshly minted operator token set (aud: iam-platform) and the rotated refresh token in the body (not a cookie). Response is no-store (Cache-Control: no-store); no Set-Cookie header — this operator channel returns the rotated refresh token in the body. On reuse/invalid token the endpoint returns 401 INVALID_CREDENTIALS instead.

data.accessTokenJWTalways
Freshly minted operator access token (aud: iam-platform). Auto-captured by the tester as the active bearer token for subsequent admin/operator calls.
data.tokenTypeenumalways
Token scheme to use in the Authorization header; combine as 'Bearer <accessToken>'.
  • BearerAlways Bearer — send the access token as 'Authorization: Bearer <accessToken>'.
data.expiresInintegeralways
Lifetime of the new access token in seconds (e.g. 600 = 10 minutes); use to schedule the next refresh before expiry.
data.refreshTokenstringalways
The rotated operator refresh token — the old one is now marked used. Store this and feed it into the next POST /v1/admin/auth/refresh call; replaying the old (used) token triggers reuse-detection and revokes the whole operator session.
POST/v1/admin/auth/logoutadminOperator logout

The only operator-auth endpoint that requires an operator x-gateway-context (sends the captured bearer). No body. Revokes the current operator session (keyed by sid).

ℹ︎Field guide — what each value means & where it comes fromno inputs

No request body, no path/query params, and no caller-set headers. It is authenticated: it still requires a valid operator x-gateway-context (handled by the tester + gateway), and revokes the operator session identified by the sid inside that context.

Bearer token
POST https://api.kerja.team/v1/admin/auth/logout
↩︎Response guide — what comes back & what each value meansno body

200 OK with the { data } envelope, but data is an empty object ({ "data": {} }) — there are no body properties to consume. Success simply confirms the operator session keyed by the context's `sid` was revoked (subsequent refreshes for that session now fail; audits `operator_logout`). No notable response headers (no Set-Cookie / Cache-Control documented).