> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.vitablehealth.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.vitablehealth.com/_mcp/server.

# Authentication

> How to authenticate with Vitable's API using API keys and bound access tokens.

Vitable uses API keys for trusted server-to-server requests and bound access tokens for Vitable Drops running in a browser.

| Credential                  | Use it from                  | Scope             | Typical use                                                |
| --------------------------- | ---------------------------- | ----------------- | ---------------------------------------------------------- |
| API key                     | Your backend only            | Your organization | Call the Vitable Connect API and issue bound access tokens |
| Employee-bound access token | Your frontend through a Drop | One employee      | Render the Employee Dashboard                              |
| Employer-bound access token | Your frontend through a Drop | One employer      | Render Employer Benefits or Employer Billing               |

## Get an API Key

[Contact the Vitable team](mailto:dev@vitablehealth.com) to request credentials. Store the API key in a secret manager or protected environment variable and use a different key for each environment when available.

> **Warning**
>
> Never expose an API key in frontend code, a mobile application, logs, or a public repository. Treat it like a password.

## Authenticate Server Requests

Send the API key as a Bearer token in the `Authorization` header:

```bash
curl https://api.vitablehealth.com/v1/employers \
  --header "Authorization: Bearer $VITABLE_API_KEY" \
  --header "Content-Type: application/json"
```

```python
import os
import requests

response = requests.get(
    "https://api.vitablehealth.com/v1/employers",
    headers={"Authorization": f"Bearer {os.environ['VITABLE_API_KEY']}"},
)
response.raise_for_status()

data = response.json()
```

```javascript
const response = await fetch("https://api.vitablehealth.com/v1/employers", {
  headers: { Authorization: `Bearer ${process.env.VITABLE_API_KEY}` },
})

if (!response.ok) {
  throw new Error(`Vitable request failed: ${response.status}`)
}

const data = await response.json()
```

> **Note**
>
> All timestamps in API requests and responses use ISO 8601 in UTC, such as `2026-01-23T14:30:00+00:00`.

## Issue Bound Access Tokens

A Drop must never receive your API key. Instead, your backend calls [`POST /v1/auth/access-tokens`](https://developer.vitablehealth.com/api/auth/issue-access-token) and returns the short-lived result to your frontend.

Choose a `bound_entity` that matches the widget:

| Widget             | `bound_entity.type` | `bound_entity.id`                          |
| ------------------ | ------------------- | ------------------------------------------ |
| Employee Dashboard | `employee`          | Vitable employee ID, such as `empl_abc123` |
| Employer Benefits  | `employer`          | Vitable employer ID, such as `empr_abc123` |
| Employer Billing   | `employer`          | Vitable employer ID, such as `empr_abc123` |

```bash
curl --request POST https://api.vitablehealth.com/v1/auth/access-tokens \
  --header "Authorization: Bearer $VITABLE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "grant_type": "client_credentials",
    "bound_entity": {
      "type": "employee",
      "id": "empl_abc123"
    }
  }'
```

The response contains `access_token` and `expires_in`. Do not assume a fixed lifetime: pass both values to the Drops SDK so it can refresh the session before expiration.

```json
{
  "access_token": "vit_at_...",
  "expires_in": 910
}
```

See [Set Up Vitable Drops](/drops/get-started) for a complete backend endpoint and provider configuration.

## Handle Rate Limits

When a request exceeds its rate limit, the API returns `429 Too Many Requests`. Inspect these response headers instead of assuming a fixed limit:

| Header                  | Meaning                                       |
| ----------------------- | --------------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests allowed in the active window |
| `X-RateLimit-Remaining` | Requests remaining in that window             |
| `X-RateLimit-Reset`     | When the limit resets                         |
| `RateLimit-Reason`      | Why the request was limited, when provided    |

Retry `429` responses with exponential backoff and jitter. For lifecycle changes, use [webhooks](/webhooks) instead of frequent polling.

## Security Checklist

* Keep API calls and bound-token issuance on an authenticated backend.
* Authorize the signed-in user before issuing a token for an employee or employer.
* Return only the bound token and its expiration to the browser.
* Restrict CORS and protect token endpoints against cross-site request forgery.
* Do not log credentials or place them in query strings.