<!-- Carebit docs: Authentication -->

# Authentication

Carebit uses OAuth2. Every developer project owns one or more **API
credentials** (`client_id` + `client_secret` pairs), and every access
token is scoped to a single credential.

## Credential prefixes

Carebit prefixes every secret so leaked values can be detected and
classified. Customer credentials use these prefixes:

| Kind                   | Prefix                   |
| ---------------------- | ------------------------ |
| Client secret          | `carebit_cs_live_...`    |
| Access token           | `carebit_at_live_...`    |
| Refresh token          | `carebit_rt_live_...`    |
| Webhook signing secret | `carebit_whsec_live_...` |

`client_id` is the OAuth application UID. Do not depend on any specific
prefix for `client_id`.

Never commit these values. Rotate a credential immediately if you suspect
it has leaked.

## Client-credentials grant

The token endpoint is `POST /oauth/token`. It accepts
`application/x-www-form-urlencoded` per the OAuth2 spec.

<!-- code-tabs -->

### cURL

```bash
curl -X POST "https://api.carebit.co/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=$CAREBIT_CLIENT_ID" \
  --data-urlencode "client_secret=$CAREBIT_CLIENT_SECRET" \
  --data-urlencode "scope=bookings.read bookings.create"
```

### JavaScript

```javascript
const body = new URLSearchParams({
  grant_type: "client_credentials",
  client_id: process.env.CAREBIT_CLIENT_ID,
  client_secret: process.env.CAREBIT_CLIENT_SECRET,
  scope: "bookings.read bookings.create",
});

const response = await fetch("https://api.carebit.co/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body,
});
const { access_token, refresh_token } = await response.json();
```

### Python

```python
import os
import requests

response = requests.post(
    "https://api.carebit.co/oauth/token",
    data={
        "grant_type": "client_credentials",
        "client_id": os.environ["CAREBIT_CLIENT_ID"],
        "client_secret": os.environ["CAREBIT_CLIENT_SECRET"],
        "scope": "bookings.read bookings.create",
    },
)
tokens = response.json()
access_token = tokens["access_token"]
refresh_token = tokens["refresh_token"]
```

### Ruby

```ruby
require "httparty"

response = HTTParty.post(
  "https://api.carebit.co/oauth/token",
  body: {
    grant_type: "client_credentials",
    client_id: ENV.fetch("CAREBIT_CLIENT_ID"),
    client_secret: ENV.fetch("CAREBIT_CLIENT_SECRET"),
    scope: "bookings.read bookings.create"
  }
)
tokens = response.parsed_response
access_token = tokens.fetch("access_token")
refresh_token = tokens.fetch("refresh_token")
```

### PHP

```php
$client = new GuzzleHttp\Client();

$response = $client->post("https://api.carebit.co/oauth/token", [
    "form_params" => [
        "grant_type" => "client_credentials",
        "client_id" => getenv("CAREBIT_CLIENT_ID"),
        "client_secret" => getenv("CAREBIT_CLIENT_SECRET"),
        "scope" => "bookings.read bookings.create",
    ],
]);
$data = json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
$accessToken = $data["access_token"];
$refreshToken = $data["refresh_token"];
```

<!-- /code-tabs -->

Response:

```json
{
  "access_token": "carebit_at_live_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "created_at": 1767193200,
  "refresh_token": "carebit_rt_live_...",
  "scope": "bookings.read bookings.create"
}
```

## Refreshing tokens

Refresh tokens rotate on every use. Store the new `refresh_token` from
each response and discard the old one.

<!-- code-tabs -->

### cURL

```bash
curl -X POST "https://api.carebit.co/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "client_id=$CAREBIT_CLIENT_ID" \
  --data-urlencode "client_secret=$CAREBIT_CLIENT_SECRET" \
  --data-urlencode "refresh_token=$CAREBIT_REFRESH_TOKEN"
```

### JavaScript

```javascript
const body = new URLSearchParams({
  grant_type: "refresh_token",
  client_id: process.env.CAREBIT_CLIENT_ID,
  client_secret: process.env.CAREBIT_CLIENT_SECRET,
  refresh_token: process.env.CAREBIT_REFRESH_TOKEN,
});

const response = await fetch("https://api.carebit.co/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body,
});
const { access_token, refresh_token } = await response.json();
```

### Python

```python
import os
import requests

response = requests.post(
    "https://api.carebit.co/oauth/token",
    data={
        "grant_type": "refresh_token",
        "client_id": os.environ["CAREBIT_CLIENT_ID"],
        "client_secret": os.environ["CAREBIT_CLIENT_SECRET"],
        "refresh_token": os.environ["CAREBIT_REFRESH_TOKEN"],
    },
)
tokens = response.json()
access_token = tokens["access_token"]
refresh_token = tokens["refresh_token"]
```

### Ruby

```ruby
require "httparty"

response = HTTParty.post(
  "https://api.carebit.co/oauth/token",
  body: {
    grant_type: "refresh_token",
    client_id: ENV.fetch("CAREBIT_CLIENT_ID"),
    client_secret: ENV.fetch("CAREBIT_CLIENT_SECRET"),
    refresh_token: ENV.fetch("CAREBIT_REFRESH_TOKEN")
  }
)
tokens = response.parsed_response
access_token = tokens.fetch("access_token")
refresh_token = tokens.fetch("refresh_token")
```

### PHP

```php
$client = new GuzzleHttp\Client();

$response = $client->post("https://api.carebit.co/oauth/token", [
    "form_params" => [
        "grant_type" => "refresh_token",
        "client_id" => getenv("CAREBIT_CLIENT_ID"),
        "client_secret" => getenv("CAREBIT_CLIENT_SECRET"),
        "refresh_token" => getenv("CAREBIT_REFRESH_TOKEN"),
    ],
]);
$data = json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
$accessToken = $data["access_token"];
$refreshToken = $data["refresh_token"];
```

<!-- /code-tabs -->

## Revoking a token

`POST /oauth/revoke` immediately invalidates an access or refresh token.

<!-- code-tabs -->

### cURL

```bash
curl -X POST "https://api.carebit.co/oauth/revoke" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=$CAREBIT_CLIENT_ID" \
  --data-urlencode "client_secret=$CAREBIT_CLIENT_SECRET" \
  --data-urlencode "token=$CAREBIT_ACCESS_TOKEN" \
  --data-urlencode "token_type_hint=access_token"
```

### JavaScript

```javascript
const body = new URLSearchParams({
  client_id: process.env.CAREBIT_CLIENT_ID,
  client_secret: process.env.CAREBIT_CLIENT_SECRET,
  token: process.env.CAREBIT_ACCESS_TOKEN,
  token_type_hint: "access_token",
});

await fetch("https://api.carebit.co/oauth/revoke", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body,
});
```

### Python

```python
import os
import requests

requests.post(
    "https://api.carebit.co/oauth/revoke",
    data={
        "client_id": os.environ["CAREBIT_CLIENT_ID"],
        "client_secret": os.environ["CAREBIT_CLIENT_SECRET"],
        "token": os.environ["CAREBIT_ACCESS_TOKEN"],
        "token_type_hint": "access_token",
    },
)
```

### Ruby

```ruby
require "httparty"

HTTParty.post(
  "https://api.carebit.co/oauth/revoke",
  body: {
    client_id: ENV.fetch("CAREBIT_CLIENT_ID"),
    client_secret: ENV.fetch("CAREBIT_CLIENT_SECRET"),
    token: ENV.fetch("CAREBIT_ACCESS_TOKEN"),
    token_type_hint: "access_token"
  }
)
```

### PHP

```php
$client = new GuzzleHttp\Client();

$client->post("https://api.carebit.co/oauth/revoke", [
    "form_params" => [
        "client_id" => getenv("CAREBIT_CLIENT_ID"),
        "client_secret" => getenv("CAREBIT_CLIENT_SECRET"),
        "token" => getenv("CAREBIT_ACCESS_TOKEN"),
        "token_type_hint" => "access_token",
    ],
]);
```

<!-- /code-tabs -->

## Scopes

Scopes are granular: `resource.read`, `resource.create`, `resource.update`,
`resource.delete`. The developer project owns the maximum scope set.
Credentials do not store their own scopes.

When you request `scope` on `POST /oauth/token` with
`grant_type=client_credentials`, every requested scope must already be
on the project. The issued token receives exactly the scopes you
requested. If you omit `scope`, the token receives the project's full
scope set. Requesting a scope the project does not have returns `400`
with `error.code` `invalid_scope`.

Refresh grants ignore `scope`. The new access token keeps the scopes
from the original grant, and those scopes must still be on the project.

Removing a scope from the project takes effect on the next API request,
including for tokens that were already issued.

## Sending an access token

Send the access token as an `Authorization: Bearer` header on every API
request. Do not send the client credentials on regular API requests.

<!-- code-tabs -->

### cURL

```bash
curl "https://api.carebit.co/v1/bookings?start_time_from=2026-08-01T00:00:00Z&start_time_to=2026-08-08T00:00:00Z" \
  -H "Authorization: Bearer $CAREBIT_ACCESS_TOKEN"
```

### JavaScript

```javascript
const params = new URLSearchParams({
  start_time_from: "2026-08-01T00:00:00Z",
  start_time_to: "2026-08-08T00:00:00Z",
});
const response = await fetch(`https://api.carebit.co/v1/bookings?${params}`, {
  headers: {
    Authorization: `Bearer ${process.env.CAREBIT_ACCESS_TOKEN}`,
  },
});
const bookings = await response.json();
```

### Python

```python
import os
import requests

response = requests.get(
    "https://api.carebit.co/v1/bookings",
    headers={
        "Authorization": f"Bearer {os.environ['CAREBIT_ACCESS_TOKEN']}",
    },
    params={
        "start_time_from": "2026-08-01T00:00:00Z",
        "start_time_to": "2026-08-08T00:00:00Z",
    },
)
bookings = response.json()
```

### Ruby

```ruby
require "httparty"

response = HTTParty.get(
  "https://api.carebit.co/v1/bookings",
  headers: {
    "Authorization" => "Bearer #{ENV.fetch("CAREBIT_ACCESS_TOKEN")}"
  },
  query: {
    start_time_from: "2026-08-01T00:00:00Z",
    start_time_to: "2026-08-08T00:00:00Z"
  }
)
bookings = response.parsed_response
```

### PHP

```php
$client = new GuzzleHttp\Client();

$response = $client->get("https://api.carebit.co/v1/bookings", [
    "headers" => [
        "Authorization" => "Bearer " . getenv("CAREBIT_ACCESS_TOKEN"),
    ],
    "query" => [
        "start_time_from" => "2026-08-01T00:00:00Z",
        "start_time_to" => "2026-08-08T00:00:00Z",
    ],
]);
$bookings = json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
```

<!-- /code-tabs -->
