Support acts inside a tenant (act-as)
Spans both profiles. Mint a short-lived, tenant-scoped token on the control plane — the cross-tenant decision is made once, here: audited, time-bounded, reason-bound — then use it against the public surface. Requires support-acting-in-tenant.
- POST /v1/admin/act-as → 200 (mint tenant-scoped accessToken; tenant must be active)
- (public profile) present the minted token to the public surface; actions audited with actOperatorId / actReason
- POST /v1/admin/act-as/refresh → renew before expiry with a fresh actReason
- POST /v1/admin/act-as/end → optional early end (audit marker)
/v1/admin/act-asadminBegin act-as sessionNeeds Idempotency-Key: <uuid>. Response (Cache-Control: no-store) carries data.accessToken — auto-captured as the active bearer token (then usable against the public surface). Target tenant must be active (else 422). Add a stepUp factor to the body if your operator auth is stale.
Field guide — what each value means & where it comes from6 fields
Begins an act-as session, minting a short-lived tenant-scoped token. Requires the operator's gateway context (handled by the tester/gateway) plus the support-acting-in-tenant permission; you must send a fresh Idempotency-Key UUID. Step-up (password or recovery code) is required only when the operator's auth is stale.
Idempotency-KeyheaderUUIDrequiredclient-generatedtenantIdbodyUUIDrequiredfrom a responseactReasonbodystringrequiredyou choosestepUpbodyobjectconditionalyou choosestepUp.passwordbodystringconditionalyou choosestepUp.recoveryCodebodystringconditionalauthenticatorPOST https://api.kerja.team/v1/admin/act-asResponse guide — what comes back & what each value means5 fields
200 OK returning the { data } envelope (an ActAsToken object); response carries Cache-Control: no-store and no Set-Cookie. Body is never empty on success.
data.tokenTypeenumalwaysBearer— Always Bearer — use as Authorization: Bearer <accessToken>.
data.expiresInintegeralwaysdata.accessTokenJWTalwaysdata.actAsTenantUUIDalwaysdata.actReasonstringalways/v1/admin/act-as/refreshadminRefresh act-as sessionNeeds Idempotency-Key: <uuid>. Requires a fresh actReason — never a silent renewal. New accessToken is auto-captured. Same tenant-active / step-up-freshness rules as begin.
Field guide — what each value means & where it comes from6 fields
Renews an act-as session with a fresh actReason (never silent). Requires the support-acting-in-tenant role via gateway context, plus a client-generated Idempotency-Key header. Send a JSON body with actAsTenant + actReason; a stepUp factor is required only if the operator's auth has gone stale (older than STEP_UP_FRESHNESS_SECONDS, default 300s). Authorization / x-gateway-context are handled by the tester + gateway.
Idempotency-KeyheaderUUIDrequiredclient-generatedactAsTenantbodyUUIDrequiredfrom a responseactReasonbodystringrequiredyou choosestepUpbodyobjectconditionalyou choosestepUp.passwordbodystringconditionalyou choosestepUp.recoveryCodebodystringconditionalauthenticatorPOST https://api.kerja.team/v1/admin/act-as/refreshResponse guide — what comes back & what each value means5 fields
200 OK returning the { data } envelope with the renewed ActAsToken object; response carries Cache-Control: no-store (no Set-Cookie). The same step-up-freshness and tenant-active rules as begin apply, and a fresh actReason is always required (never a silent renewal).
data.tokenTypeenumalwaysBearer— Standard bearer scheme — send as 'Authorization: Bearer <accessToken>'.
data.expiresInintegeralwaysdata.accessTokenJWTalwaysdata.actAsTenantUUIDalwaysdata.actReasonstringalways/v1/admin/act-as/endadminEnd act-as sessionAn audit marker only (no server-side session store; sessions auto-end at TTL). No Idempotency-Key. actReason is optional.
Field guide — what each value means & where it comes from2 fields
JSON body with actAsTenant (required) plus optional actReason; this is just an audit marker (sessions auto-end at token TTL). No Idempotency-Key and no no-store header. Still requires the act-as gateway context with the support-acting-in-tenant role (handled by the tester/gateway).
actAsTenantbodyUUIDrequiredfrom a responseactReasonbodystringoptionalyou choosePOST https://api.kerja.team/v1/admin/act-as/endResponse guide — what comes back & what each value means2 fields
200 OK with the { data } envelope. No notable response headers — no Set-Cookie and no Cache-Control: no-store (this endpoint is an audit marker only, not a token-issuing route). The body confirms the act-as session was marked ended.
data.actAsTenantUUIDalwaysdata.endedbooleanalwaystrue— The act-as session end was audited (event act_as_ended). This is the only value returned on success.
/v1/auditadminConfirm the attribution in the tenant's own logThe public-profile read (scenario 51), called here with the MINTED TENANT TOKEN rather than the operator one — which is the point of step 2. Every action taken while acting-as lands in the TENANT's log carrying actOperatorId / actReason, so the tenant admin sees who reached in and why. That visibility is what makes act-as an accountable support tool rather than a back door: the operator cannot act inside a tenant without leaving a signed reason in the log that tenant reads.
Field guide — what each value means & where it comes from6 fields
No request body — all inputs are optional query filters. Admin-only and tenant-scoped; still requires the tester's Authorization (access token) and gateway context, which the tester and gateway supply.
eventquerystringoptionalyou chooseuserIdqueryUUIDoptionalfrom a responsefromqueryISO-8601optionalyou choosetoqueryISO-8601optionalyou chooselimitqueryintegeroptionalyou choosecursorquerystringoptionalfrom a responseGET https://api.kerja.team/v1/audit?limit=20Response guide — what comes back & what each value means16 fields
200 OK with the standard { data, meta } envelope. data is an ARRAY of audit-log event objects (cursor-paginated); meta.page carries the limit and the opaque nextCursor. Response is no-store; no Set-Cookie. SCOPE — an event belongs to this log if it is stamped with the caller's tenant OR is an IDENTITY-PLANE event (tenant_id NULL) about somebody who is a member of it. The second half exists because authentication events happen BEFORE any tenant is resolved: a device challenge is issued while the user is still proving who they are, so device_challenge_issued / device_trust_failed / login_challenge_failed / registration_resumed / login_mode_changed carry no tenant. Filtering on tenant_id alone hid device_trust_failed — a correct password followed by repeated device-challenge failures, the fingerprint of credential stuffing landing on a real account — from the only person positioned to act on it. Nothing from another tenant can leak: anything tenant-scoped carries a non-NULL tenant_id and is excluded unless it is this one.
dataarrayalwaysdata[].idstringalwaysdata[].eventstringalwaysdata[].userIdUUIDalwaysdata[].sessionIdstringalwaysdata[].outcomeenumalwayssuccess— The audited action completed successfully.failure— The audited action was attempted but did not succeed.
data[].reasonstringalwaysdata[].ipstringalwaysdata[].traceIdstringalwaysdata[].actOperatorIdUUIDalwaysdata[].actReasonstringalwaysdata[].occurredAtISO-8601alwaysmetaobjectalwaysmeta.pageobjectalwaysmeta.page.limitintegeralwaysmeta.page.nextCursorstringalways