# Introspect

Ask whether a token is still live, and what it carries.

Access tokens are opaque strings, so there is nothing to decode locally. When
you need to know whether one is still valid — and which scopes and user it
belongs to — ask this endpoint. Follows RFC 7662.

**POST** `/api/auth/oauth2/introspect`

> [!TIP]
> You rarely need this in a normal login integration: just call the token
> endpoint and let a `401` tell you the token is gone. Introspection is for
> resource servers that are handed a token by someone else and have to validate
> it on every request.

## Authorizations

| Name | Type | In | Required | Description |
| --- | --- | --- | --- | --- |
| `Authorization` | string | header | no | `Basic base64(client_id:client_secret)`. Alternatively send both as form fields. |
| `client_id` | string | body | yes | Your app's client ID. Both the id and the secret are required here — there is no anonymous introspection. |
| `client_secret` | string | body | yes | Your app's secret. |

## Body

The body must be `application/x-www-form-urlencoded`.

| Name | Type | In | Required | Description |
| --- | --- | --- | --- | --- |
| `token` | string | body | yes | The access token or refresh token to inspect. |
| `token_type_hint` | string | body | no | `access_token` or `refresh_token`. Only a hint. |

## Example Request

  #### cURL

```bash
curl -X POST "https://haunt.gg/api/auth/oauth2/introspect" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "token=YOUR_ACCESS_TOKEN"
```
  #### JavaScript

```ts
const res = await fetch("https://haunt.gg/api/auth/oauth2/introspect", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
  },
  body: new URLSearchParams({ token: accessToken }),
});
const { active, scope, sub } = await res.json();
```

## Response

  #### 200 — The token is live and belongs to your app.

```json
{
  "active": true,
  "iss": "https://haunt.gg",
  "client_id": "YOUR_CLIENT_ID",
  "sub": "clz9k2x0a0000s601f8h3d7q2",
  "scope": "openid identify offline_access",
  "iat": 1767222000,
  "exp": 1767225600
}
```
  #### 200 — Everything else. An unknown, expired, revoked or foreign token — including one issued to a different app, or one belonging to an app that was suspended — is reported as inactive rather than as an error, so it never leaks whether a token exists.

```json
{
  "active": false
}
```
  #### 400 — No token was sent.

```json
{
  "error": "invalid_request",
  "error_description": "missing a required token for introspection"
}
```
  #### 401 — Client authentication is missing or wrong.

```json
{
  "error": "invalid_client",
  "error_description": "missing required credentials"
}
```

## Rate limit

100 requests per minute per IP address, counted for this endpoint alone.
