> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getkato.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Exchange or refresh tokens

> Exchange an authorization code or rotate a refresh token for a registered client.

`POST https://api.getkato.io/oauth/token`

Exchange an authorization code or rotate a refresh token for a registered client.

See [OAuth flow and credentials](/api-reference/oauth/overview) for client registration, authorization, and token rotation.

Accepts JSON or application/x-www-form-urlencoded. No bearer Authorization header or client secret is required. Code exchange validates membership, client, redirect, and PKCE. Refresh rotates the refresh token and revokes previous access tokens for the grant; store both returned tokens atomically and serialize refresh attempts. Refresh does not extend the original refresh expiry.

## Parameters

<ParamField body="grant_type" type="string" required>
  authorization\_code or refresh\_token.
</ParamField>

<ParamField body="client_id" type="string" required>
  Registered client ID, 1–100 characters.
</ParamField>

<ParamField body="code" type="string">
  Required for authorization\_code; 20–300 characters, unused and unexpired.
</ParamField>

<ParamField body="code_verifier" type="string">
  Required for authorization\_code; 43–128 characters matching the original S256 challenge.
</ParamField>

<ParamField body="redirect_uri" type="string">
  Required for authorization\_code; exact original allowed URL, up to 1,000 characters.
</ParamField>

<ParamField body="refresh_token" type="string">
  Required for refresh\_token; current token, 20–300 characters.
</ParamField>

## Response

**200** — Top-level access\_token, token\_type, expires\_in (seconds), refresh\_token, refresh\_token\_expires\_in (remaining seconds), and space-delimited scope. Responses set Cache-Control: no-store.

```json 200 theme={null}
{
  "access_token": "EXAMPLE_ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "EXAMPLE_REFRESH_TOKEN",
  "refresh_token_expires_in": 31536000,
  "scope": "tasks:read tasks:write records:read objects:read meetings:read activity:read notifications:read notifications:write comments:write"
}
```

```bash Request theme={null}
curl --fail-with-body --silent --show-error \
  "https://api.getkato.io/oauth/token" \
  -H "Content-Type: application/json" \
  --data '{
  "grant_type": "refresh_token",
  "client_id": "kato-raycast",
  "refresh_token": "REPLACE_WITH_CURRENT_REFRESH_TOKEN"
}'
```

## Compatibility alias

`POST /oauth/raycast/token` calls the same handler. Use `/oauth/token` for new clients.

## Authorization-code exchange

```json theme={null}
{
  "grant_type": "authorization_code",
  "client_id": "kato-raycast",
  "code": "REPLACE_WITH_AUTHORIZATION_CODE",
  "code_verifier": "REPLACE_WITH_ORIGINAL_43_TO_128_CHARACTER_VERIFIER",
  "redirect_uri": "https://raycast.com/redirect?packageName=kato"
}
```

Errors use `error` and `error_description`: `invalid_request`, `invalid_client`, `invalid_grant`, or `unsupported_grant_type`. Invalid, expired, reused codes or refresh tokens require recovery through the authorization flow when no current credential remains.


## Related topics

- [OAuth authorization](/api-reference/oauth/overview.md)
- [All endpoints](/api-reference/endpoints.md)
- [Raycast API](/api-reference/raycast/overview.md)
- [Revoke an OAuth grant](/api-reference/oauth/revoke.md)
- [Connect Google Calendar](/integrations/google-calendar.md)
