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

# Get an Access Token

> Exchange an authorization code, client credentials, or refresh token for an access token

The token endpoint issues the bearer tokens used on `/v2` endpoints. It supports three grants:
`authorization_code`, `client_credentials`, and `refresh_token`.

## Endpoint

```
POST /oauth/token
```

## Authentication

The request authenticates your **application**, not a user. Send credentials in the form body
(`client_secret_post`):

* **Confidential clients** must send `client_id` and a matching `client_secret`.
* **Public clients** send `client_id` only and prove possession through PKCE.

<Warning>
  HTTP Basic authentication is not supported. Client credentials must be form fields in the request
  body.
</Warning>

## Request Body

Content type: `application/x-www-form-urlencoded`

| Parameter       | Type   | Required    | Description                                                                                                                 |
| --------------- | ------ | ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| `grant_type`    | string | Yes         | `authorization_code`, `client_credentials`, or `refresh_token`                                                              |
| `client_id`     | string | Yes         | Your application's client ID (`cid_...`)                                                                                    |
| `client_secret` | string | Conditional | Required for confidential clients. Omit for public clients                                                                  |
| `code`          | string | Conditional | Required for `authorization_code` - the code from the authorize redirect                                                    |
| `redirect_uri`  | string | Conditional | Required for `authorization_code`. Must match the value used at `/oauth/authorize` exactly                                  |
| `code_verifier` | string | Conditional | Required for `authorization_code` - the PKCE verifier whose SHA-256 you sent as `code_challenge`                            |
| `refresh_token` | string | Conditional | Required for `refresh_token`                                                                                                |
| `scope`         | string | No          | `client_credentials` only. Space-delimited subset of the application's allowed scopes. Defaults to all of them when omitted |

### Grants

<Tabs>
  <Tab title="authorization_code">
    Exchanges a single-use code from [`/oauth/authorize`](/api-reference/oauth/authorize) for a token
    bound to the user who consented. This is the grant to use for anything calling `/v2`.

    Required: `grant_type`, `client_id`, `code`, `redirect_uri`, `code_verifier` (plus `client_secret`
    for confidential clients).

    The granted scopes are the ones the user approved - you cannot widen them here.
  </Tab>

  <Tab title="client_credentials">
    Authenticates the application itself, with no end user involved in the exchange. The token acts as
    the admin who created the application and carries that admin's access.

    Required: `grant_type`, `client_id`, `client_secret`. The application must be a **confidential**
    client and must have `client_credentials` among its grant types.

    <Warning>
      The creating admin sets the ceiling on what these tokens can reach, and deactivating that user
      revokes them. See [Client credentials tokens](/api-reference/oauth-apps#client-credentials-tokens).
    </Warning>

    No refresh token is ever issued for this grant; request a new access token instead.
  </Tab>

  <Tab title="refresh_token">
    Exchanges a refresh token for a new access token, carrying over the original scopes and user.

    Required: `grant_type`, `client_id`, `refresh_token` (plus `client_secret` for confidential clients).

    <Warning>
      Refreshing **rotates the pair**: the old access token and the old refresh token are both revoked
      immediately. Store the new `refresh_token` from the response - the previous one no longer works.
    </Warning>

    A refresh token only exists if the application set `default_token_ttl_seconds`. Applications without
    a token TTL receive non-expiring access tokens and no refresh token.
  </Tab>
</Tabs>

## Request Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://platform.chamelio.ai/oauth/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=authorization_code" \
    -d "client_id=cid_your_client_id" \
    -d "client_secret=ca_your_client_secret" \
    -d "code=ca_single_use_code" \
    -d "redirect_uri=https://your-app.example.com/callback" \
    -d "code_verifier=your_pkce_code_verifier"
  ```

  ```python Python theme={null}
  import requests

  url = "https://platform.chamelio.ai/oauth/token"
  data = {
      "grant_type": "authorization_code",
      "client_id": "cid_your_client_id",
      "client_secret": "ca_your_client_secret",
      "code": "ca_single_use_code",
      "redirect_uri": "https://your-app.example.com/callback",
      "code_verifier": "your_pkce_code_verifier",
  }

  response = requests.post(url, data=data)
  token = response.json()
  print(token["access_token"])
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://platform.chamelio.ai/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: 'cid_your_client_id',
      client_secret: 'ca_your_client_secret',
      code: 'ca_single_use_code',
      redirect_uri: 'https://your-app.example.com/callback',
      code_verifier: 'your_pkce_code_verifier'
    })
  });

  const token = await response.json();
  console.log(token.access_token);
  ```
</CodeGroup>

## Response

### Success Response

**Status Code:** `200 OK`

```json theme={null}
{
  "access_token": "ca_xYz123AbCdEfGhIjKlMnOpQrStUvWxYz",
  "token_type": "Bearer",
  "scope": "tasks:read tasks:write",
  "expires_in": 3600,
  "refresh_token": "ca_AbCdEfGhIjKlMnOpQrStUvWxYz123xYz"
}
```

For an application without a token TTL, the response omits both optional fields entirely:

```json theme={null}
{
  "access_token": "ca_xYz123AbCdEfGhIjKlMnOpQrStUvWxYz",
  "token_type": "Bearer",
  "scope": "tasks:read tasks:write"
}
```

### Response Fields

| Field           | Type    | Description                                                                                       |
| --------------- | ------- | ------------------------------------------------------------------------------------------------- |
| `access_token`  | string  | The bearer token to send as `Authorization: Bearer ...`                                           |
| `token_type`    | string  | Always `Bearer`                                                                                   |
| `scope`         | string  | Space-delimited scopes actually granted to this token                                             |
| `expires_in`    | integer | Lifetime in seconds. Omitted when the token does not expire                                       |
| `refresh_token` | string  | Omitted unless the application set a finite token TTL, and never present for `client_credentials` |

## Error Responses

Errors follow the OAuth error format rather than Chamelio's usual `detail` body:

```json theme={null}
{
  "error": "invalid_grant",
  "error_description": "PKCE verification failed"
}
```

### 401 Unauthorized

| `error`          | Reason                                                                                                                                                    |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_client` | `client_id` missing, unknown, or inactive; wrong or missing `client_secret` for a confidential client; or a public client attempting `client_credentials` |

### 400 Bad Request

| `error`                  | Reason                                                                                                                                                           |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request`        | A required parameter for the grant is missing                                                                                                                    |
| `invalid_grant`          | The code or refresh token is unknown, already used, expired, revoked, issued to a different client, has a mismatched `redirect_uri`, or failed PKCE verification |
| `unauthorized_client`    | The application is not allowed the requested grant                                                                                                               |
| `invalid_scope`          | Requested scope is unknown or exceeds the application's allowed scopes                                                                                           |
| `unsupported_grant_type` | `grant_type` is not one of the three supported values                                                                                                            |

## Notes

<Warning>
  Store access and refresh tokens as securely as passwords. Chamelio keeps only a hash, so a token
  value cannot be recovered - losing it means requesting a new one.
</Warning>

<Info>
  Scopes are fixed at issuance. To gain an additional scope, send the user through
  [`/oauth/authorize`](/api-reference/oauth/authorize) again with the wider `scope` value.
</Info>

<Tip>
  Verify a fresh token with
  [`GET /v2/users/user-info`](/api-reference/endpoint/v2/users/user-info) - it echoes back the user,
  organization, and granted scopes the token resolves to.
</Tip>

## Use Cases

* **Completing user sign-in** - Turn an authorization code into a usable token
* **Keeping a session alive** - Rotate a short-lived token before it expires
* **Confirming a credential** - Check that a client ID and secret pair still authenticate
