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

# Authentication

> API key format, scopes, rotation, and the BAA gate for PHI.

Every request to the Partner API is authenticated with your API key as a Bearer token:

```bash theme={null}
curl https://voice.growdental.ai/api/partner/v1/practices \
  -H "Authorization: Bearer gd_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
```

There are no cookies, sessions, or OAuth flows — one header, every request, always over HTTPS.

## Key format

```text theme={null}
gd_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
└┬┘ └┬─┘ └──────────────────┬────────────────────┘
 │   │                      └ 43-char base62 secret (~256 bits)
 │   └ environment (live; test reserved for a future sandbox)
 └ GrowDental prefix
```

* Keys are issued by the GrowDental team and shown **exactly once** at creation. We store only a SHA-256 hash — a lost key cannot be recovered, only replaced.
* Store keys in a secret manager. Never commit them, log them, or ship them to browsers or mobile apps; all calls should come from your servers.
* Only `live` keys are issued today. The `gd_test_` prefix is reserved for a future sandbox environment.

## Scopes

Each key carries a set of scopes. Requests that need a scope your key lacks fail with `403 insufficient_scope`. Practice grants can further narrow scopes per practice — a request that clears your key's scopes can still be refused for a practice whose grant does not include that scope.

| Scope                 | Grants                                             | PHI |
| --------------------- | -------------------------------------------------- | --- |
| `practices:read`      | List granted practices                             | —   |
| `calls:read`          | Call outcomes, durations, summaries                | —   |
| `transcripts:read`    | Call transcripts                                   | Yes |
| `recordings:read`     | Call recording audio                               | Yes |
| `call-requests:write` | Create and read your call requests                 | —   |
| `webhooks:manage`     | Webhook endpoint CRUD                              | —   |
| `intake:write`        | Batch patient/appointment/treatment push (Phase 2) | Yes |
| `scheduling:manage`   | Mid-call scheduling integration (Phase 3)          | —   |

Ask for the narrowest scope set that covers your integration — adding a scope later is a quick, non-breaking change on our side.

## The BAA gate

Scopes marked **PHI** above are hard-gated in code, not just in paperwork: until a Business Associate Agreement is executed for your app, any request using them returns:

```json theme={null}
{
  "error": "baa_required",
  "message": "The scope recordings:read exposes PHI and requires a signed BAA. Contact support to complete your BAA."
}
```

Everything else (outcomes, summaries, call triggering, webhooks) works without a BAA, so you can build and test most of an integration while the agreement is in flight. Contact [partners@growdental.ai](mailto:partners@growdental.ai) to start the BAA process.

## Key rotation

Rotation is a coordinated, zero-downtime handoff:

<Steps>
  <Step title="Request a new key">
    Email us (or use your admin contact) to issue a second key with the same
    scopes. Both keys work simultaneously — there is no forced cutover.
  </Step>

  <Step title="Deploy the new key">
    Roll the new key out through your secret manager and verify traffic
    succeeds with it (watch for 401s).
  </Step>

  <Step title="Revoke the old key">
    Tell us to revoke the old key. Revocation is immediate; revoked keys get
    `401 key_revoked` on every request.
  </Step>
</Steps>

Rotate immediately if a key may have been exposed (committed to a repo, printed in logs, shared over an insecure channel). We also track `lastUsedAt` per key and may flag long-unused keys for cleanup.

## Authentication errors

| Status | `error` code            | Meaning                                                                                   |
| ------ | ----------------------- | ----------------------------------------------------------------------------------------- |
| 401    | `missing_authorization` | No or malformed `Authorization: Bearer ...` header                                        |
| 401    | `invalid_api_key`       | Key is unknown (check for truncation/whitespace)                                          |
| 401    | `key_revoked`           | Key was revoked — switch to your current key                                              |
| 403    | `app_suspended`         | Your partner app is suspended — contact us                                                |
| 403    | `insufficient_scope`    | Key lacks the required scope                                                              |
| 403    | `baa_required`          | PHI scope used without a signed BAA                                                       |
| 404    | `not_found`             | Resource missing **or** practice not granted to your app (deliberately indistinguishable) |

## Rate limits

Limits are per key, per minute, on a fixed window:

* **600 requests/minute** for most endpoints.
* **60 requests/minute** for bulk writes (call-request creation) — a separate budget, so bulk traffic never starves your reads.

Exceeding a limit returns `429 rate_limited` with a `Retry-After` header (seconds). Back off for at least that long; requests sent while limited still count against the window.
