switching clears the token & responses
No token
37

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.

  1. POST /v1/admin/act-as → 200 (mint tenant-scoped accessToken; tenant must be active)
  2. (public profile) present the minted token to the public surface; actions audited with actOperatorId / actReason
  3. POST /v1/admin/act-as/refresh → renew before expiry with a fresh actReason
  4. POST /v1/admin/act-as/end → optional early end (audit marker)
POST/v1/admin/act-asadminBegin act-as session

Needs 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.

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎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-generated
You generate a fresh UUID for each begin attempt. The same key with a different body returns 409; reusing it replays the prior result (within the token TTL window). Makes the mint operation replay-safe.
tenantIdbodyUUIDrequiredfrom a response
The target tenant to act inside. Obtain it from a prior tenant list/get response in the tester (a tenant's id field), not typed from memory. The tenant must be active or the call returns 422.
actReasonbodystringrequiredyou choose
You type the justification for acting as the tenant, trimmed, 1-500 chars (e.g. a support-ticket id like 'SUP-1043 investigate billing'). Recorded in the token's act_reason claim and the audit trail.
stepUpbodyobjectconditionalyou choose
Step-up re-auth envelope { password?, recoveryCode? }. Required only if the operator's auth is stale; omit it when auth is fresh. If stale and omitted, the call returns 401.
stepUp.passwordbodystringconditionalyou choose
The operator's current account password, typed to satisfy step-up. Supply this OR stepUp.recoveryCode when the operator's auth is stale.
stepUp.recoveryCodebodystringconditionalauthenticator
A one-time recovery code from the operator's MFA authenticator backup codes. Supply this OR stepUp.password when the operator's auth is stale.
Bearer token
POST https://api.kerja.team/v1/admin/act-as
↩︎Response 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.tokenTypeenumalways
The token scheme for the minted act-as bearer token. The client prefixes the accessToken with this when building the Authorization header.
  • BearerAlways Bearer — use as Authorization: Bearer <accessToken>.
data.expiresInintegeralways
Lifetime of the act-as token in seconds (ACT_AS_TTL_SECONDS, default 3600, capped by MAX_TTL_SECONDS). The client should treat the token as expired after this many seconds and refresh via POST /v1/admin/act-as/refresh.
data.accessTokenJWTalways
The short-lived, tenant-scoped JWT (sub = operator, tenant_id = target tenant, RFC 8693 act envelope, plus act_as_tenant and act_reason claims). Auto-captured by the tester as the active bearer token for calls against the public tenant-facing surface.
data.actAsTenantUUIDalways
The target tenant id this act-as session is scoped to. Feed into POST /v1/admin/act-as/refresh as actAsTenant when renewing the session.
data.actReasonstringalways
The operator-supplied reason echoed back (e.g. a support-ticket id), recorded in the audit trail. Confirms which justification was bound to this session.
POST/v1/admin/act-as/refreshadminRefresh act-as session

Needs 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.

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎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-generated
You generate a fresh UUID for each refresh request. Lets the server safely de-duplicate retries of this mutating call.
actAsTenantbodyUUIDrequiredfrom a response
The tenant whose act-as session is being renewed. Copy it from the prior act-as begin response (data.actAsTenant) or the token you are renewing; it is not freely typed.
actReasonbodystringrequiredyou choose
You type a fresh justification (e.g. a support ticket reference) for continuing the act-as session. Trimmed, 1-500 chars; a new reason is mandatory on every renewal — there is no silent renewal.
stepUpbodyobjectconditionalyou choose
A fresh re-authentication factor, re-verified inside the handler. Required only if (iff) the operator's auth is stale — i.e. they did not authenticate within STEP_UP_FRESHNESS_SECONDS (default 300s); omit it otherwise. The object is .strict() and must contain at least one of password or recoveryCode.
stepUp.passwordbodystringconditionalyou choose
The operator's current account password, typed by the user. Provide it (or stepUp.recoveryCode) when stepUp is required by a stale-auth condition. 1-1024 chars.
stepUp.recoveryCodebodystringconditionalauthenticator
A one-time recovery code from the operator's MFA authenticator/backup codes. Provide it (or stepUp.password) when stepUp is required by a stale-auth condition. 1-128 chars.
Bearer token
POST https://api.kerja.team/v1/admin/act-as/refresh
↩︎Response 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.tokenTypeenumalways
Token scheme for the renewed act-as access token. Combine with data.accessToken to form the Authorization header value (e.g. 'Bearer eyJ…').
  • BearerStandard bearer scheme — send as 'Authorization: Bearer <accessToken>'.
data.expiresInintegeralways
Lifetime of the renewed act-as access token in seconds (e.g. 3600 = 1 hour). The act-as session auto-ends when this TTL elapses; refresh again before then to extend.
data.accessTokenJWTalways
Freshly re-minted act-as bearer JWT scoped to the target tenant. Auto-captured by the tester as the active bearer token for subsequent acting-in-tenant calls.
data.actAsTenantUUIDalways
The tenant the operator is now acting as (echoes the request's actAsTenant). Confirms which tenant context the renewed token applies to.
data.actReasonstringalways
The fresh justification recorded for this renewal (echoes the request's actReason); audited as part of the act_as_refreshed event. Renewals are never silent — a new reason is always required.
POST/v1/admin/act-as/endadminEnd act-as session

An audit marker only (no server-side session store; sessions auto-end at TTL). No Idempotency-Key. actReason is optional.

Request body (JSON)
ℹ︎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 response
The tenant UUID of the act-as session you are ending. You obtain this from the prior POST /v1/admin/act-as response (the actAsTenant you began acting in, §3.68), or from your current act-as token context. Identifies which act-as session to mark ended.
actReasonbodystringoptionalyou choose
Optional free-text reason you type for ending the session (trimmed, 1-500 chars), e.g. a ticket reference. If omitted, the audit defaults the reason to "act-as session ended". Recorded on the act_as_ended audit entry.
Bearer token
POST https://api.kerja.team/v1/admin/act-as/end
↩︎Response 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.actAsTenantUUIDalways
The tenant whose act-as session was ended — echoes back the actAsTenant UUID sent in the request, so the client can confirm which impersonation context was closed.
data.endedbooleanalways
Confirmation flag that the act-as session was recorded as ended (an audit marker — there is no server-side session store, so this simply acknowledges the act_as_ended audit event was written). Always true on a 200 success.
  • trueThe act-as session end was audited (event act_as_ended). This is the only value returned on success.
GET/v1/auditadminConfirm the attribution in the tenant's own log

The 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.

Query parameters
ℹ︎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 choose
You type the exact audit event name to filter by (trimmed, 1-64 chars). Matches a single event such as login_succeeded. Leave empty to return all event types.
userIdqueryUUIDoptionalfrom a response
UUID of the user whose audit events you want. Obtain it from a prior list/get response (e.g. a user list's id field) and paste it here to scope results to that user.
fromqueryISO-8601optionalyou choose
You pick the start of the time window (ISO-8601 date, between 2000-01-01 and 2100-01-01). Only events at or after this time are returned.
toqueryISO-8601optionalyou choose
You pick the end of the time window (ISO-8601, same bounds). Must be greater than or equal to from, otherwise a 422 validation_error is returned. Only events at or before this time are returned.
limitqueryintegeroptionalyou choose
You choose how many records per page (integer 1-100, default 20). Controls page size for cursor pagination.
cursorquerystringoptionalfrom a response
Opaque pagination cursor (<=512 chars). Copy it from the previous response's meta.page.nextCursor to fetch the next page; omit for the first page.
Bearer token
GET https://api.kerja.team/v1/audit?limit=20
↩︎Response 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.

dataarrayalways
Array of security audit-log event records for the caller's tenant, newest-first; may be empty if no events match the filters.
data[].idstringalways
Opaque identifier of this audit event row.
data[].eventstringalways
The exact event name that was recorded (e.g. login_succeeded); pass back as the ?event filter to narrow subsequent queries.
data[].userIdUUIDalways
ID of the user the event concerns; feed into the ?userId filter to scope the log to one user.
data[].sessionIdstringalways
Identifier of the session associated with the event, when applicable.
data[].outcomeenumalways
Whether the audited action succeeded or failed.
  • successThe audited action completed successfully.
  • failureThe audited action was attempted but did not succeed.
data[].reasonstringalways
Human/machine reason or failure code for the event; null when there is no associated reason (e.g. a plain success).
data[].ipstringalways
Source IP address recorded for the event.
data[].traceIdstringalways
Distributed-trace correlation id for the request that produced this event; use to cross-reference logs/traces.
data[].actOperatorIdUUIDalways
When the action was performed by an operator acting inside the tenant (act-as), the operator's id; null for ordinary tenant-user actions.
data[].actReasonstringalways
Reason the operator supplied for the act-as session; null when the event was not an act-as action.
data[].occurredAtISO-8601always
UTC timestamp of when the event occurred; use for time-ordering and as the basis for the ?from/?to date filters.
metaobjectalways
Envelope metadata wrapper for the paged result.
meta.pageobjectalways
Pagination block for this audit page.
meta.page.limitintegeralways
The page size applied for this query (1-100, default 20); echoes the ?limit param.
meta.page.nextCursorstringalways
Opaque keyset cursor for the next page, or null when this is the last page; pass back as ?cursor to fetch the following page.