> ## 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.

# API Reference

> Base URL, conventions, errors, and pagination for the GrowDental Partner API v1.

The reference pages in this tab are generated from the published OpenAPI 3.1 specification — the contract of record for v1. Mintlify also exposes the spec for tooling (and an `llms.txt` for AI assistants) automatically.

## Base URL

```text theme={null}
https://voice.growdental.ai/api/partner/v1
```

The version lives in the path. Backwards-compatible changes (new fields, new enum values, new endpoints) ship without a version bump — write your parsers to ignore unknown fields and tolerate new enum values. Breaking changes get a new path version and a [changelog](/changelog) entry.

## Authentication

Every request needs your API key as a Bearer token:

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

See the [authentication guide](/guides/authentication) for scopes, rotation, and the BAA gate.

## Conventions

* JSON in, JSON out; property names are **snake\_case** (`duration_seconds`, `next_cursor`).
* Timestamps are ISO 8601 / RFC 3339 UTC strings; phone numbers are E.164.
* `GET` endpoints never take request bodies — all filters are query parameters.
* Practices your app has not been granted return **404, never 403** — practice IDs cannot be probed.
* Every `POST` honors an `Idempotency-Key` header (24-hour replay window).

## Errors

All non-2xx responses share one envelope:

```json theme={null}
{
  "error": "insufficient_scope",
  "message": "This API key does not have the required scope: calls:read"
}
```

`error` is a stable machine-readable code; `message` is human-readable and may change — parse the code, not the message.

| Status | Codes                                                                                           |
| ------ | ----------------------------------------------------------------------------------------------- |
| 400    | `validation_error`, `invalid_json`, `invalid_request`, `invalid_cursor`, `duplicate_contacts`   |
| 401    | `missing_authorization`, `invalid_api_key`, `key_revoked`, `invalid_token`\*, `token_expired`\* |
| 403    | `app_suspended`, `insufficient_scope`, `baa_required`                                           |
| 404    | `not_found` (includes ungranted practices, deliberately)                                        |
| 409    | `idempotency_conflict`, `queue_full`, `insufficient_minute_balance`, `limit_exceeded`           |
| 429    | `rate_limited` (honor `Retry-After`), `daily_cap_exceeded`                                      |
| 500    | `internal_error`, `not_configured`                                                              |
| 502    | `recording_unavailable`\*                                                                       |

\* Returned only by `GET /recordings/stream` — the signed-URL target of the `GET /calls/{callId}/recording` redirect.

## Pagination

Pagination varies by resource:

**Calls** (`GET /practices/{practiceId}/calls`) use cursor pagination — `limit` (1–100, default 50) and an opaque `cursor` in, and out:

```json theme={null}
{
  "data": [ ... ],
  "next_cursor": "eyJjIjoiMjAyNi0wNy0wNF..."
}
```

Pass `next_cursor` back as `cursor` for the next page; `next_cursor` is `null` on the last page. Treat cursors as opaque and short-lived — don't store them or construct them.

**Call requests** (`GET /practices/{practiceId}/call-requests`) use `limit` (1–100, default 20) and `offset`, and return:

```json theme={null}
{
  "call_requests": [ ... ],
  "pagination": { "limit": 20, "offset": 0, "total_count": 42 }
}
```

**Practices** (`GET /practices`) and **webhook endpoints** (`GET /webhook-endpoints`) return the full set as `{ "data": [ ... ] }` — no pagination.

## Rate limits

600 requests/minute per key; bulk writes (call-request creation) have a separate 60/minute budget. `429` responses carry `Retry-After` (seconds).
