# Check a token

# Check a token

Returns what a bearer token actually grants right now: its client, club, scopes and expiry. It needs no
scope of its own — any valid token can call it. Useful as a health check, or to debug "why am I getting
403s" by comparing the `scope` field here against what a call actually needs — see [Scopes](/docs/scopes).

## Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | yes | `Bearer <access_token>` |

#### Request

```bash
curl -s https://sail-club-server.cloud.run/api/v1/token-info \
  -H "Authorization: Bearer $TOKEN"
```

```javascript
const res = await fetch('https://sail-club-server.cloud.run/api/v1/token-info', {
  headers: { Authorization: `Bearer ${token}` },
});
const info = await res.json();
```

```python
res = requests.get(
    "https://sail-club-server.cloud.run/api/v1/token-info",
    headers={"Authorization": f"Bearer {token}"},
    timeout=10,
)
info = res.json()
```

```csharp
var res = await client.GetAsync("/api/v1/token-info");
res.EnsureSuccessStatusCode();
var info = await res.Content.ReadFromJsonAsync<TokenInfoDto>();
```

```php
<?php
$ch = curl_init('https://sail-club-server.cloud.run/api/v1/token-info');
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"]]);
$info = json_decode(curl_exec($ch), true);
```

#### Response

```json
{
  "client_id": "scc_3f9a1c0d2b7e4a6f8c1d2e3f",
  "club_id": "TRX1001CL",
  "scope": "events:read results:read",
  "expires_at": "2026-09-30T10:00:00Z"
}
```

`200` — `Cache-Control: no-store` (this reflects the token's live state, so it is never cached).

## Response fields

| Field | Type | Note |
|---|---|---|
| `client_id` | string | the token's client (`scc_…`) |
| `club_id` | string | the club the token's data calls are scoped to |
| `scope` | string | the token's actual, space-separated scopes (may be narrower than the client's) |
| `expires_at` | string (ISO 8601 UTC) | when this token stops working |

## Errors

| HTTP | Code |
|---|---|
| 401 | [`API_TOKEN_MISSING`](/docs/errors#API_TOKEN_MISSING) / [`API_TOKEN_INVALID`](/docs/errors#API_TOKEN_INVALID) / [`API_CLIENT_DISABLED`](/docs/errors#API_CLIENT_DISABLED) / [`API_CLIENT_EXPIRED`](/docs/errors#API_CLIENT_EXPIRED) |
| 403 | [`API_IP_NOT_ALLOWED`](/docs/errors#API_IP_NOT_ALLOWED) |

## Caching

Never cached (`no-store`) — it exists to show live state, so a stale copy would defeat its purpose. Call it
only when you actually need to check a token, not on every request.

```prompt
Implement getTokenInfo() for the Sail Club API — a lightweight check of a bearer token's own state, useful
for a health check or to debug "why am I getting 403s".

GET https://sail-club-server.cloud.run/api/v1/token-info
Authorization: Bearer <access_token>

Success 200 (Cache-Control: no-store, never cache this): { "client_id", "club_id", "scope", "expires_at" }.
This endpoint needs no scope of its own — any valid token can call it.
401 API_TOKEN_MISSING/API_TOKEN_INVALID/API_CLIENT_DISABLED/API_CLIENT_EXPIRED, 403 API_IP_NOT_ALLOWED: same
handling as every other call — see the full-integration prompt on /docs/overview.

Adapt to your actual stack — the endpoint and response shape above are language-neutral.
```
