---
title: Managing sessions
description: The lifecycle endpoints after the initial login — refresh,
  introspect, logout, and revoke-all on the Connect API.
editUrl: true
head: []
template: doc
sidebar:
  order: 4
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

import { Tabs, TabItem } from "@astrojs/starlight/components";

A login is the start of a session, not the end of the integration. Connect provides four endpoints for the rest of the session lifecycle: **`/refresh`**, **`/introspect`**, **`/logout`**, and **`/revoke-all`**. This page is the single reference for all of them.

If you're using the OIDC flow, see also [`/end-session` in the OIDC guide](/en-us/oidc/flow/#5-end-session) — it has a related but narrower purpose.

## At a glance

| Endpoint | Authenticates with | Idempotent | Scope of effect |
|---|---|---|---|
| `POST /refresh` | The refresh token itself | No (rotates the refresh token — each token works exactly once) | One session |
| `POST /introspect` | The access token itself | Read-only | One session |
| `POST /logout` | The refresh token itself | Yes (calling twice returns `revoked: true` both times) | One session |
| `POST /revoke-all` | Client-auth JWT (RS256) | Yes | All sessions for an account, scoped to the calling app |

None of these endpoints require setting up new infrastructure — they reuse the keys you already have from your initial integration.

## `/refresh` — extend a session

Exchange a refresh token for a fresh access token **and a new refresh token**. Refresh is **strict rotation** ([OAuth 2.1 BCP §4.14.2](https://www.rfc-editor.org/rfc/rfc9700#section-4.14.2)): the refresh token you present is consumed and invalidated in the same call, and the response hands you its replacement. Store the new `refreshToken` and use it for the next refresh. Re-presenting an already-used refresh token is treated as family compromise — the entire refresh-token family is revoked and the user must re-authenticate. Near-simultaneous concurrent refreshes of the *same* token (e.g. two browser tabs at once) are the exception: they converge on a single new session rather than logging the user out. Reusing a token *after* its replacement has been issued still triggers compromise, so always store and send the latest rotated token regardless.

```bash
curl -X POST https://connect-api.sudomimus.com/refresh \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "..." }'
```

Response:

```json
{
  "accessToken": "<JWT>",
  "refreshToken": "<rotated JWT>",
  "claims": {
    "email":     { "requirement": "REQUIRED", "state": "GRANTED" },
    "firstName": { "requirement": "OPTIONAL", "state": "GRANTED" },
    "lastName":  { "requirement": "OFF",      "state": "UNKNOWN" }
  }
}
```

Persist the rotated `refreshToken`, replacing the one you just used — the old one is now invalid. The `claims` block is the same per-claim view returned by `/redeem` — see [the `claims` block](/en-us/concepts/identity-claims/#the-claims-block) for how to read it.

**Auth**: none — possession of the refresh token is the credential.

The new access token's TTL is the one resolved on the original `/redeem` (or `/direct-issue/*`, or OIDC `/token`). It is not re-resolved on refresh.

### Refresh can fail on claims

`/refresh` is not only a success-or-revoke endpoint. If, since the last token was minted, a **required** claim has stopped being satisfied — the developer escalated a claim policy from optional to required, or the user revoked a grant — the refresh is rejected with `ClaimConsentRequired` rather than minting a token missing that required claim.

Recovery depends on the client, because `/refresh` itself cannot collect consent:

- **Native clients** (Steam / AccessKey) recover by re-running the original direct-issue, which returns an [Errand](/en-us/native/claims-and-errand/) handoff for the user to grant consent.
- **Browser applications** recover by sending the user through an ordinary interactive login again.

This is rare in practice — it only happens when a policy or grant changes mid-session — but build your refresh path to surface a re-authentication prompt rather than treating every refresh failure as a hard logout.

## `/introspect` — is this token still valid?

Ask Sudomimus about the current status of an access token. Use this when you want to invalidate sessions promptly across services — e.g. when a user clicks "log out everywhere" and you need other tabs or services to notice within a bounded time.

```bash
curl -X POST https://connect-api.sudomimus.com/introspect \
  -H "Content-Type: application/json" \
  -d '{ "accessToken": "..." }'
```

Response:

```json
{
  "status": "active",
  "recommendedRecheckSeconds": 600
}
```

**`status`** is one of:

- `"active"` — the underlying refresh token exists, is not suspended, and is not expired.
- `"revoked"` — the underlying refresh token has been explicitly suspended (via `/logout` or `/revoke-all`).
- `"expired"` — the underlying refresh token is past its `expiredAt` date.
- `"not_found"` — the token does not match any record under the calling application, or the token cannot be parsed.

**`recommendedRecheckSeconds`** is how long Sudomimus suggests you may cache the result before re-introspecting. It is always 600 seconds today. Treat the access token's own `exp` claim as the upper bound on its usable lifetime; introspection is for catching *early* revocation.

**Auth**: none — the access token is self-authenticating. Anyone holding the token can ask whether it is still valid; nothing else is required.

### When to call introspect

Local signature verification covers correctness; introspection covers freshness. A reasonable pattern:

- Verify the access token's signature and `exp` claim **on every request** (cheap, local).
- Call `/introspect` opportunistically — once per N minutes per session, on a background job, or when a user-visible state change suggests it.

You do not need to introspect on every request. Doing so would defeat the point of having a signed token in the first place.

## `/logout` — invalidate a single session

Suspend one refresh token. The token's access tokens stop being reported as active by `/introspect`, and `/refresh` against it will fail.

```bash
curl -X POST https://connect-api.sudomimus.com/logout \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "..." }'
```

Response:

```json
{ "revoked": true }
```

- `revoked: true` — the token was suspended (or was already suspended/expired; calling twice is fine).
- `revoked: false` — the token is invalid or not found.

**Auth**: none — possession of the refresh token authorizes logging out that session ([RFC 7009](https://www.rfc-editor.org/rfc/rfc7009) style).

:::note[`/logout` does not nuke access tokens cryptographically]
A `/logout` call suspends the refresh-token record. Already-issued access tokens remain syntactically valid until their `exp` — the only way other services notice is by calling `/introspect` (or by the access token expiring and `/refresh` failing). Build your "log out everywhere" UX with that in mind.
:::

## `/revoke-all` — kill every session for an account

Suspend every refresh token issued to a specific account, within the calling application. Use this for account-takeover incident response, "log me out of all devices", or support-initiated session termination.

The account is identified by its **sector subject** — the `subject` value your application sees on its tokens (the `sub` you key your users on), not the raw account UUID. Sudomimus reverse-maps it to the underlying account internally.

```bash
curl -X POST https://connect-api.sudomimus.com/revoke-all \
  -H "Content-Type: application/json" \
  -H "Authorization: SudomimusClientJWT $SUDOMIMUS_CLIENT_AUTH_JWT" \
  -d '{ "subject": "sub_9SQ5535CRWNDDM2T" }'
```

Response:

```json
{ "revokedCount": 3 }
```

`revokedCount` is the number of refresh tokens that were suspended by this call. Already-suspended or already-expired tokens are not double-counted.

**Auth**: a client-auth JWT, signed exactly like the one for `/establish`. The scope of the action is implicit — only sessions issued to that account *within the calling application* are touched. You cannot use one application's client-auth key to revoke sessions in another application.

## Putting it together — a typical session lifecycle

```
   /redeem ──► access (3h)   refresh (30d)
                 │              │
                 │              │
                 ▼              │
        used on every request   │
                 │              │
                 ▼  expires     │
        /refresh ◄──────────────┤  new access + rotated refresh
                 │              │
                 ▼              │
                 │              │
   user clicks "log out" ──► /logout
                                │
                                ▼
                          revoked
                                │
                  /introspect ──► "revoked"
```

For the OIDC variant of refresh, see [OIDC relying parties — Refresh](/en-us/oidc/flow/#4-refresh).