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

# OAuth 2.0

> Client credentials for integrations that prefer short-lived tokens over long-lived API keys.

The API accepts two bearer credentials interchangeably: long-lived API keys
(`ck_live_…`, see [Authentication](/authentication)) and short-lived OAuth
access tokens (`ck_oat_…`). OAuth is the better fit when your platform already
manages OAuth credentials, or when you want tokens that expire on their own
instead of a key that lives until revoked.

The server implements the standard **client credentials** grant
([RFC 6749 §4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4)),
token revocation ([RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009)),
and discovery metadata at `/.well-known/oauth-authorization-server`
([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)).

## 1. Register a client

An owner or admin registers an OAuth client for the business (same policy as
API keys — see [who can create keys](/authentication#keys)). Registration
returns:

| Credential    | Shape         | Notes                                             |
| ------------- | ------------- | ------------------------------------------------- |
| Client ID     | `ck_client_…` | Public identifier — safe to log and display       |
| Client secret | `ck_secret_…` | Shown **exactly once**; Cherry stores only a hash |

Like an API key, a client is pinned to one business and owned by the member who
created it. Its effective scopes are clamped to that member's current role on
every request.

## 2. Exchange for a token

`POST /oauth/token` at the API root (not under `/v1`). Authenticate with HTTP
Basic (recommended) or `client_id`/`client_secret` form parameters:

```bash theme={null}
curl -s https://api.trycherry.ai/oauth/token \
  -u "ck_client_…:ck_secret_…" \
  -d grant_type=client_credentials \
  -d scope=read
```

```json theme={null}
{
  "access_token": "ck_oat_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read"
}
```

`scope` is optional and space-delimited; omit it to receive every scope the
client holds. Requesting a scope the client wasn't granted returns
`invalid_scope` — never a silent narrowing.

## 3. Call the API

Send the token exactly like an API key, on any `/v1` endpoint or the MCP
server:

```bash theme={null}
curl -s https://api.trycherry.ai/v1/transactions \
  -H "Authorization: Bearer ck_oat_…"
```

Scope rules, error shapes, and rate limits are identical to API-key requests.
Tokens expire after **one hour** — fetch a new one when you receive `401`, and
don't cache tokens past `expires_in`.

## Revocation

* `POST /oauth/revoke` with `token=ck_oat_…` (client-authenticated, RFC 7009)
  kills one token immediately. A client can only revoke its own tokens.
* Revoking the **client** in Cherry kills every outstanding token instantly and
  stops future issuance.

## Authorization code + PKCE (per-user consent)

Use this flow when your product's users connect their own Cherry business —
a **"Connect Cherry"** button instead of copy-pasted credentials. It requires
redirect URIs registered on the client (exact-match, `https` only except
`localhost`/`127.0.0.1`), and PKCE with **S256** is mandatory.

1. **Send the user to the consent page** on the Cherry web app:

   ```
   https://app.trycherry.ai/oauth/authorize
     ?client_id=ck_client_…
     &redirect_uri=https://partner.example/callback
     &scope=read
     &state=af0ifjsldkj
     &code_challenge=E9Melhoa…
     &code_challenge_method=S256
   ```

   The user signs in, sees your client's name, the requested scopes, and
   which business the grant binds, and approves. The scopes a user can
   approve are capped by their own role — a read-only member cannot grant
   `write`.

2. **Receive the code.** The browser returns to your `redirect_uri` with
   `?code=ck_ac_…&state=…`. Codes are single-use and expire in 5 minutes.

3. **Exchange it** (server-side, with your client secret and the PKCE
   verifier):

   ```bash theme={null}
   curl -s https://api.trycherry.ai/oauth/token \
     -u "ck_client_…:ck_secret_…" \
     -d grant_type=authorization_code \
     -d code=ck_ac_… \
     -d redirect_uri=https://partner.example/callback \
     -d code_verifier=dBjftJeZ…
   ```

   The response includes a `refresh_token` (`ck_rt_…`) alongside the usual
   access token.

4. **Refresh.** Refresh tokens rotate on every use — each refresh response
   carries a **new** `refresh_token`, and the old one dies. Presenting a
   retired refresh token is treated as theft and answers `invalid_grant`.

   ```bash theme={null}
   curl -s https://api.trycherry.ai/oauth/token \
     -u "ck_client_…:ck_secret_…" \
     -d grant_type=refresh_token \
     -d refresh_token=ck_rt_…
   ```

Tokens from this flow act **as the consenting user**: requests are
attributed to them, and the token's scopes are clamped to their current
role on every call — if they leave the business, the grant stops working.
`POST /oauth/revoke` accepts `ck_rt_…` refresh tokens as well as access
tokens.

## Token endpoint errors

| Error                          | Meaning                                                                      |
| ------------------------------ | ---------------------------------------------------------------------------- |
| `invalid_client` (401)         | Unknown client, wrong secret, or revoked client                              |
| `unsupported_grant_type` (400) | Grant other than `client_credentials`, `authorization_code`, `refresh_token` |
| `invalid_grant` (400)          | Bad, expired, or replayed code; failed PKCE; retired refresh token           |
| `invalid_scope` (400)          | Requested scope beyond the client's grant                                    |
| `rate_limited` (429)           | Token endpoint budget exhausted; retry after `Retry-After`                   |
