# Session tokens (REST API)

The REST API your backend calls to create, inspect and invalidate chat sessions.

```text
1. Your backend  -> POST /api/v1/chat/sessions        (identity + lifetime)
2. Your backend  <- signed JWT + session record
3. Your backend  -> return the token with your own session
4. Your frontend -> hand the token to the chat SDK
5. Chat SDK      -> the customer chats, already identified
6. Before expiry -> DELETE the session, POST a new one, pass it to setUserToken
7. On sign-out   -> DELETE /api/v1/chat/sessions?customerId=...
```

## Endpoints

| Method | Path | Purpose |
|---|---|---|
| `POST` | `/api/v1/chat/sessions` | Create a session and get its token |
| `GET` | `/api/v1/chat/sessions?customerId=` | The customer's live session, if any |
| `GET` | `/api/v1/chat/sessions/{sessionId}` | Has this session been invalidated? |
| `DELETE` | `/api/v1/chat/sessions/{sessionId}?customerId=` | Invalidate that session |
| `DELETE` | `/api/v1/chat/sessions?customerId=` | Invalidate whatever session the customer holds |

Every endpoint requires a **secret-bearing API key** (`x-api-key: keyId:secret`) and is server-to-server only. A bare key ID is refused.

## Lifetime is yours to choose

`expiresInSeconds` accepts **60 to 86400** (24 hours), defaulting to 3600. Want six-hour sessions? Send `21600`. Values above the maximum are refused rather than silently clamped, so you always know what you got.

## One live session per customer

A customer holds at most one session. `POST` returns **409** with the offending session while one is still valid; mint again once it expires or after you revoke it. This is deliberate: it keeps one identity from accumulating parallel credentials, and it means "the customer's session" is always a single, knowable thing.

## How little is stored

The token is a **JWT**, and it *is* the session: the customer id, name, email, phone and metadata are all signed into it. Verifying the signature yields the whole identity, so the platform keeps no copy of any of it. Duplicating it server side would only create state that could drift from the claims.

Redis holds two things, and nothing else:

| Key | Written when | Lives for |
|---|---|---|
| `chat:revoked:{org}:{jti}` | **only** when you invalidate a session early | the token's remaining lifetime |
| `chat:live:{org}:{customerId}` | on every mint, to enforce the one-session rule | the token's lifetime |

A session that lives out its natural life is never written to the denylist at all. Both keys carry the token's own TTL, so the store empties itself and nothing has to be swept.

A **denylist** rather than an allowlist is deliberate: if Redis is briefly unreachable, an allowlist would reject every token and take chat down, while a denylist degrades to "an early revocation may be missed until Redis returns", bounded by the lifetime you chose. Short lifetimes make that window small, which is another reason to prefer them.

The token itself is never stored either. If you lose it, revoke the session and mint another.

**GET /api/v1/chat/sessions**: Get a customer's live session (fetch id `endpoint:6ae152d083f212a997c813bf`)

**POST /api/v1/chat/sessions**: Create a chat session (fetch id `endpoint:6ae152d083f212a997c813c0`)

**DELETE /api/v1/chat/sessions**: Invalidate a customer's session (fetch id `endpoint:6ae152d083f212a997c813c1`)

**GET /api/v1/chat/sessions/{chatSessionId}**: Get one session (fetch id `endpoint:6ae152d083f212a997c813c2`)

**DELETE /api/v1/chat/sessions/{chatSessionId}**: Invalidate a session (fetch id `endpoint:6ae152d083f212a997c813c3`)
