Every login creates a session on AuthPI, and sessions are what you act on when someone reports a stolen laptop or you offboard an employee. This guide covers the session API — listing, revoking, suspending — and, just as importantly, exactly what each action does and doesn't do to the user's tokens.
What a session is
A session ties together one authentication event: who logged in (user_id), how (authentication.method), from where (device, location), through which OAuth client, and which tokens it has issued (refresh_count, token issuance timestamps — JTIs are tracked, token values are never stored). What the API returns is a public projection: the cookie-verification secret AuthPI holds internally is never included in session responses. Sessions move through a small lifecycle:
| Status | Meaning |
|---|---|
active | Normal — the session can refresh tokens |
suspended | Temporarily frozen; reactivatable |
revoked | Terminated by you or the user — terminal |
expired | Aged out — terminal |
Defaults: sessions live 7 days, idle-expire after 12 hours without activity, and each user holds at most 50 sessions — creating a 51st auto-expires the oldest. Both lifetimes are configurable per issuer (settings.access.sessions.max_age / idle_timeout, in seconds, capped at 1 year and 30 days respectively); a session past either limit can no longer refresh — absolute expiry applies even if the session is still inside its idle window.
AuthPI also distinguishes session type in API responses. An op session is the SSO anchor held by AuthPI; an rp session belongs to a specific OAuth client and links back to that OP session. Revoking an OP session also revokes its linked RP sessions for the same user and issuer, so an SSO logout can fan out to every client session created from that browser login.
List a user's sessions
import { AuthPIAdmin } from "@authpi/admin";
const admin = new AuthPIAdmin({
apiKey: { id: process.env.AUTHPI_KEY_ID!, secret: process.env.AUTHPI_KEY_SECRET! },
accountId: "acc_7k2m9x4p1q8w5e3r6t0y2u4i8",
});
const page = await admin
.issuer("i_4r8w2k9m5x1p7q3e6t0y2u4i8")
.user("usr_8t2y6u4i0o5p3a7s1d9f2g4h6")
.sessions.list({ status: "active" });
console.log(page.data); // PublicSession[] import os
from authpi_admin import AuthPIAdmin
async with AuthPIAdmin(
api_key=(os.environ["AUTHPI_KEY_ID"], os.environ["AUTHPI_KEY_SECRET"]),
account_id="acc_7k2m9x4p1q8w5e3r6t0y2u4i8",
) as admin:
page = await (
admin.issuer("i_4r8w2k9m5x1p7q3e6t0y2u4i8")
.user("usr_8t2y6u4i0o5p3a7s1d9f2g4h6")
.sessions.list("active")
)
print(page.data) curl "https://api.authpi.com/v1/accounts/acc_7k2m9x4p1q8w5e3r6t0y2u4i8/issuers/i_4r8w2k9m5x1p7q3e6t0y2u4i8/users/usr_8t2y6u4i0o5p3a7s1d9f2g4h6/sessions?status=active" \
-u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" Each record carries the device and location captured at login — enough to build a "your active sessions" page where users spot the login that isn't theirs.
Revoke a session
await admin
.issuer("i_4r8w2k9m5x1p7q3e6t0y2u4i8")
.sessions.revoke("ses_3f7a1k9m2x5p8q4e6t0y2u4i7", {
reason: "security_event",
}); await admin.issuer("i_4r8w2k9m5x1p7q3e6t0y2u4i8").sessions.revoke(
"ses_3f7a1k9m2x5p8q4e6t0y2u4i7",
{"reason": "security_event"},
) curl -X POST "https://api.authpi.com/v1/accounts/acc_7k2m9x4p1q8w5e3r6t0y2u4i8/issuers/i_4r8w2k9m5x1p7q3e6t0y2u4i8/sessions/ses_3f7a1k9m2x5p8q4e6t0y2u4i7/revoke" \
-u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
-H "Content-Type: application/json" \
-d '{ "reason": "security_event" }' Valid reason values: user_logout, admin_action, security_event, password_changed, inactivity, token_compromised, other. Revocation is idempotent and terminal.
Log a user out everywhere
There is no separate "revoke all" endpoint — pass revoke_all_user_sessions: true on a revoke of any one of the user's sessions (take an id from the list call), and every non-terminal session of that user is revoked. That includes active sessions, suspended sessions, and brief inactive sessions still between authorization and token issuance. If any target is an OP session, its linked RP sessions are included too:
await admin
.issuer("i_4r8w2k9m5x1p7q3e6t0y2u4i8")
.sessions.revoke("ses_3f7a1k9m2x5p8q4e6t0y2u4i7", {
reason: "security_event",
revoke_all_user_sessions: true,
}); await admin.issuer("i_4r8w2k9m5x1p7q3e6t0y2u4i8").sessions.revoke(
"ses_3f7a1k9m2x5p8q4e6t0y2u4i7",
{"reason": "security_event", "revoke_all_user_sessions": True},
) curl -X POST ".../sessions/ses_3f7a1k9m2x5p8q4e6t0y2u4i7/revoke" \
-u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET" \
-H "Content-Type: application/json" \
-d '{ "reason": "security_event", "revoke_all_user_sessions": true }' What revocation actually does
Three different things happen to the three kinds of credentials, and the differences matter:
- Refresh tokens die immediately. The session's tokens are marked revoked the moment you call revoke; the next refresh attempt fails with a session-expired error. The user cannot mint new access tokens.
- Already-issued access tokens live until they expire. Access tokens are stateless JWTs — revoking the session doesn't reach into tokens already in the wild. The exposure window is the access-token TTL, 30 minutes by default and configurable per client. This is the same bounded-staleness model as organization changes: shorten the TTL where you need a tighter bound.
- OIDC clients get a backchannel logout. If an affected session belongs to a client with a
backchannel_logout_uriconfigured, AuthPI POSTs a signed OIDClogout_token(with the session'ssid) to it, retrying up to five times. Handle it server-side to clear your own application session immediately instead of waiting out the access-token TTL. Sessions whose client has no backchannel URI get no notification — configure one for every client that maintains its own session state.
A session.terminated event also fires per revoked session — a single OP logout or "revoke all" call can therefore produce multiple events with the same revocation reason. Subscribe via webhooks for audit trails or downstream cleanup. (Suspending or reactivating emits session.updated.)
Suspend instead of revoke
Suspension freezes a session reversibly — refresh fails while suspended, but POST .../sessions/{id}/reactivate restores it. Use it when you need to pause access during an investigation without forcing the user through login again. suspend_all_user_sessions: true works like its revoke counterpart.
Built-in protections you get for free
Two session-killing behaviors run automatically, no API call needed:
- Refresh-token replay detection. Refresh tokens rotate on every use, and only the newest one is valid. If anyone presents a previous refresh token — the classic sign of a stolen token being replayed — AuthPI doesn't just reject it: it marks the whole session compromised, revokes it, and fires a
session.compromisedevent. - Device fingerprint validation (opt-in, per client): when
require_device_fingerprint_on_refreshis set in a client's session settings, a refresh arriving from a mismatched device kills the session the same way. Leave this off for confidential server-side clients — the server's user agent never matches the browser's, and every session would self-destruct on first refresh. It's meant for public clients that refresh from the browser.
The kill-switch: cutting off a compromised user completely
Revoking sessions alone is not the whole job — a user can hold credentials that aren't session-bound. The full sequence:
- Revoke all sessions —
revoke_all_user_sessions: truewith reasonsecurity_event. Kills refresh immediately, triggers backchannel logout. - Block the user —
PATCH .../users/{user_id}withstatus: "blocked". Blocking prevents new logins and stops token refresh: the refresh endpoint rejects a blocked (or suspended, or deleted) user withinvalid_grant, so blocking alone caps the user's access at the current access-token TTL. Pair it with step 1 anyway — revocation terminates the existing sessions outright and triggers backchannel logout, and access tokens already in the wild ride out their TTL either way. - Revoke personal access tokens — list the user's
ptk_tokens and revoke each (POST .../tokens/{token_id}/revoke). These are independent of sessions and survive session revocation. - Revoke trusted devices —
POST .../users/{user_id}/trusted-devices/revoke-all. Trusted devices are an MFA-skip list, not sessions — clearing them forces full MFA on the next login attempt.
After step 1, the residual exposure is in-flight access tokens (≤ the access-token TTL); backchannel logout closes even that gap for clients that implement it.
Next steps
- Token claims reference —
sid, token TTLs, and what's inside the tokens sessions issue - Organization lifecycle — the same bounded-staleness model applied to membership changes
- Webhooks — consuming
session.terminatedandsession.compromised - API keys — the credential you're calling this API with