# Introspection & Revocation

> POST /api/oauth/introspect (RFC 7662, confidential clients, caller-bound) to check a token's state, and POST /api/oauth/revoke (RFC 7009) to kill a refresh-token family. Always returns 200.

Two machine endpoints for managing token state: **introspection** asks whether a token is active (for confidential resource servers), and **revocation** ends a session by killing a refresh-token family. Both are CSRF-exempt and cookie-free.

## Introspection

```http
POST /api/oauth/introspect
Content-Type: application/x-www-form-urlencoded
```

[RFC 7662](https://www.rfc-editor.org/rfc/rfc7662). A resource server looks up a token's current state. **Confidential clients only** — the caller authenticates with its `client_secret` (HTTP Basic or `client_secret_post`), because introspection is a token-state oracle that must not be exposed to public clients.

> Most integrations do **not** need this. EntryBit access tokens are self-contained RS256 JWTs you can verify **offline** against the [JWKS](/docs/oauth/discovery-jwks/) — no network round-trip. Introspection exists for confidential clients that prefer a live check or need to inspect opaque refresh tokens.

### Request

| Field | Required | Notes |
|---|---|---|
| `token` | ✓ | The access or refresh token to inspect. |
| `client_id` |  | Client credentials (or use HTTP Basic). |
| `client_secret` |  | Client credentials (or use HTTP Basic). |

```bash
# Client secret and token stay out of the command line — export them first
curl -X POST https://entrybit.net/api/oauth/introspect \
  -u "eb_9f1c2ab34cd56ef7:$ENTRYBIT_CLIENT_SECRET" \
  --data-urlencode "token=$TOKEN_TO_INSPECT"
```

### Response

An active token returns its metadata:

```json
{
  "active": true,
  "sub": "usr_9f1c2ab34cd56ef7",
  "scope": "openid profile email passes:read",
  "client_id": "eb_9f1c2ab34cd56ef7",
  "token_type": "Bearer",
  "iss": "https://entrybit.net",
  "aud": "eb_9f1c2ab34cd56ef7",
  "exp": 1752148800,
  "iat": 1752147900
}
```

Anything else — expired, revoked, unknown, or **belonging to another client** — returns the flat, indistinguishable:

```json
{ "active": false }
```

Lookups are **bound to the calling client**: a client can only introspect tokens issued to it, and the response never distinguishes *why* a token is inactive (no enumeration oracle). A failed client authentication returns `401 invalid_client`.

## Revocation

```http
POST /api/oauth/revoke
Content-Type: application/x-www-form-urlencoded
```

[RFC 7009](https://www.rfc-editor.org/rfc/rfc7009). Revoke a **refresh token** — and with it the whole rotation family it belongs to. This is how an app signs a user out (see [Logout](/docs/oauth/logout/) for ending the *browser* session too).

### Request

| Field | Required | Notes |
|---|---|---|
| `token` | ✓ | The refresh token to revoke. |
| `client_id` |  | Client credentials (public clients send `client_id`; confidential add the secret / Basic). |
| `client_secret` |  | Confidential clients only. |

```bash
curl -X POST https://entrybit.net/api/oauth/revoke \
  --data-urlencode "token=$REFRESH_TOKEN" \
  --data-urlencode "client_id=eb_9f1c2ab34cd56ef7"
```

### Response

**Always `200`** — even for an unknown token. The server never scans or confirms token existence, so revocation cannot be used to probe for valid tokens. When the token belongs to the caller, its **entire rotation family** is revoked; if it doesn't, the call still returns `200` and does nothing.

```http
HTTP/1.1 200 OK
```

> Access tokens are **stateless** and are not revoked here — they simply expire (~15 minutes). Revoke the refresh token to end the session; any outstanding access token dies on its own shortly after.