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.
- POST /v1/admin/auth/accept-setup → set password + begin MFA enrolment (provisioningUri)
- POST /v1/admin/auth/mfa/enroll/confirm → recovery codes + activate operator
- POST /v1/admin/auth/login → mandatory MFA challenge { mfaRequired, mfaToken }
- POST /v1/admin/auth/mfa/verify → operator access token (aud: iam-platform) + refresh
- POST /v1/admin/auth/refresh → rotate refresh → fresh operator access token
- POST /v1/admin/auth/logout → revoke the current operator session
/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.
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-bandpasswordbodystringrequiredyou choosePOST https://api.kerja.team/v1/admin/auth/accept-setupResponse 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.mfaEnrollmentRequiredbooleanalwaystrue— MFA enrolment has begun and must be confirmed; the account is not yet active. This is the only value returned on success.
data.provisioningUristringalways/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.
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-bandcodebodystringrequiredauthenticatorPOST https://api.kerja.team/v1/admin/auth/mfa/enroll/confirmResponse 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.statusenumalwaysactive— Operator account is activated; TOTP enrolment confirmed and setup token consumed.
data.recoveryCodes[]arrayalways/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.
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 choosepasswordbodystringrequiredyou choosePOST https://api.kerja.team/v1/admin/auth/loginResponse 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.mfaRequiredbooleanalwaystrue— MFA 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/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.
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 responsecodebodystringrequiredauthenticatorPOST https://api.kerja.team/v1/admin/auth/mfa/verifyResponse 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.accessTokenJWTalwaysdata.tokenTypeenumalwaysBearer— Send the access token as 'Authorization: Bearer <accessToken>'.
data.expiresInintegeralwaysdata.refreshTokenstringalwaysdata.operatorobjectalwaysdata.operator.idUUIDalwaysdata.operator.emailstringalwaysdata.operator.displayNamestringalways/v1/admin/auth/refreshpublicOperator refreshPre-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.
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 responsePOST https://api.kerja.team/v1/admin/auth/refreshResponse 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.accessTokenJWTalwaysdata.tokenTypeenumalwaysBearer— Always Bearer — send the access token as 'Authorization: Bearer <accessToken>'.
data.expiresInintegeralwaysdata.refreshTokenstringalways/v1/admin/auth/logoutadminOperator logoutThe 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.
POST https://api.kerja.team/v1/admin/auth/logoutResponse 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).