# Token management

> Access token lifetime, refresh rotation, revocation, and security practices.

Source: https://sndr.sh/docs/cranl/tokens

## Access tokens

Access tokens expire after 1 hour. Pass one in every resource request:

```bash
Authorization: Bearer sndr_your_access_token
```

> **Note:** Refresh proactively at ~55 minutes rather than waiting for a 401 — this prevents failed requests during peak load.

## Refreshing tokens

Refresh tokens are valid for 30 days and are rotated on every use — the old token is immediately invalidated. Always persist the new pair.

```bash
curl -X POST 'https://api.sndr.sh/api/cranl/oauth/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'client_id=sndr_your_client_id' \
  --data-urlencode 'client_secret=sndr_your_client_secret' \
  --data-urlencode 'refresh_token=sndr_your_refresh_token'
```

```json
{
  "access_token":  "sndr_new_access_token",
  "token_type":    "Bearer",
  "expires_in":    3600,
  "refresh_token": "sndr_new_refresh_token",
  "scope":         "read send"
}
```

> **Warning:** If the refresh token expires (30 days) or is revoked, the user must complete the OAuth flow again. Plan accordingly.

## Revoking tokens

Revoke an access or refresh token immediately. Per RFC 7009, always returns 200 OK even for unknown tokens.

```bash
curl -X POST 'https://api.sndr.sh/api/cranl/oauth/revoke' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'token=sndr_token_to_revoke' \
  --data-urlencode 'client_id=sndr_your_client_id' \
  --data-urlencode 'client_secret=sndr_your_client_secret'
```

## Security checklist

- Use PKCE. Send code_challenge on authorize and code_verifier on token exchange.
- Validate state. Reject callbacks where state does not match — it is a CSRF attack.
- Secrets in a vault. Never expose client_secret or tokens in client-side code, logs, or version control.
- Token exchange is server-side only. Never call /oauth/token from the browser.
- Revoke on disconnect. Call revoke when a user disconnects your integration.
- Handle 401 gracefully. Attempt one refresh on 401 token_expired before surfacing an error.
