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

# Authentication

> API keys for integrations; OAuth for AI connectors

The REST API authenticates with an **API key** sent as a Bearer token. The MCP server accepts the same keys, and additionally OAuth 2.1 for AI clients that connect on behalf of a logged-in team member.

## API keys

<Steps>
  <Step title="Create a key">
    In PracticeHub, go to **Developers → API Keys**, choose **API key**, pick the access level — **Read & write**, or **Read only** for reporting and sync integrations that never change data — optionally an expiry (90 days or 1 year), name the integration and create the key. Only a hash is stored — the key itself is shown once, so copy it now.
  </Step>

  <Step title="Send it as a Bearer token">
    The host is the one the practice signs in on, `https://{clinic}.{region}.practicehub.io` — the region host is the one in the practice's login URL — `neptune` (London), `mercury` (Frankfurt), `vulcan` (Ohio), `jupiter` (Singapore) or `apollo` (Sydney).

    ```bash theme={null}
    curl https://your-clinic.your-region.practicehub.io/v3/api/me \
      -H "Authorization: Bearer phk_k9pQ...your-key"
    ```

    Keys look like `phk_…` — the `phk_` marker lets secret scanners (GitHub, gitleaks, truffleHog) recognise a leaked PracticeHub key.
    `GET /me` returns the integration the key belongs to, its scopes, and the account it reaches — including the **timezone every date and time on the API is expressed in** and the account currency. A good first call for any integration.

    ```json theme={null}
    {
      "data": {
        "type": "ApiApplication", "id": 12, "name": "Booking widget",
        "scopes": ["read", "write"],
        "account": { "id": 3295, "name": "Riverside Chiropractic", "timezone": "Europe/London", "currency": "GBP" }
      }
    }
    ```
  </Step>
</Steps>

Each key is one **integration** on the account: requests are logged and rate-limited per integration, and audit trails record actions as that integration (for example, an appointment log reads *"Created via API: Booking widget"*).

A **read-only** key can call every `GET` and read tool; any write returns `403` with `code: key_read_only`. Prefer read-only keys wherever an integration does not need to change data.

### Rotation, expiry and audit

* **Rotate** a key from **Developers → API Keys → ⋯ → Rotate**: you get a new secret for the same integration (same scope, same expiry date — rotation changes the secret, not the lifetime) and the previous secret keeps working for up to **24 hours** so you can switch without downtime. An expired key cannot be rotated; create a new one.
* Keys created with an expiry stop authenticating at that time (`401 unauthenticated`); rotate before then to get a fresh window.
* Every key creation, rotation and revocation — and every AI-connector approval and revocation — is recorded in the account's activity log with who did it and when.

<Warning>
  A key gives access to the account's data. Keep it server-side — never in a browser, mobile app or public repository. Revoke it from **Developers → API Keys** the moment you suspect it has leaked; revocation is immediate.
</Warning>

### Which keys an account can create

| Account                                 | PracticeHub API keys (and AI connectors, API Logs) | Legacy `/api` keys |
| --------------------------------------- | -------------------------------------------------- | ------------------ |
| Native PracticeHub 2.0 account          | Yes                                                | —                  |
| Existing account, not yet native to 2.0 | Not yet — available once the account moves to 2.0  | Yes                |

Legacy keys do not work on the new API and new keys do not work on the legacy API.

## OAuth for AI connectors

AI clients such as Claude and ChatGPT connect to the [MCP server](/mcp/overview) with OAuth 2.1 (dynamic client registration + PKCE). The client discovers the authorization server from the endpoint, registers itself, and the team member approves access on a PracticeHub consent screen. Actions then run **as that person**, with their name on the audit trail. Connecting is opt-in: account owners can always connect, other team members only if their role has **Connect AI apps** enabled (Settings → Security → Roles). See [Connectors](/mcp/connectors).

## Rate limits and request IDs

Requests are limited per integration (currently 600 per minute); `X-RateLimit-Limit` and `X-RateLimit-Remaining` are returned on every response, and `429` means wait and retry. Every response carries an `X-Request-Id`; quote it when contacting support — the same id is shown on the account's **Developers → API Logs** page.

## Errors

| Status | Meaning                                      |
| ------ | -------------------------------------------- |
| `401`  | Missing, invalid or revoked key              |
| `403`  | The key is read-only and the request writes  |
| `404`  | No such record on this account               |
| `405`  | The resource does not support that operation |
| `409`  | A business rule refused the change           |
| `422`  | Invalid input — see `errors`                 |
| `429`  | Rate limit exceeded                          |
