Skip to main content
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

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

Authentication

Every request needs your API key as a Bearer token:
curl https://voice.growdental.ai/api/partner/v1/practices \
  -H "Authorization: Bearer $GROWDENTAL_API_KEY"
See the authentication guide 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:
{
  "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.
StatusCodes
400validation_error, invalid_json, invalid_request, invalid_cursor, duplicate_contacts
401missing_authorization, invalid_api_key, key_revoked, invalid_token*, token_expired*
403app_suspended, insufficient_scope, baa_required
404not_found (includes ungranted practices, deliberately)
409idempotency_conflict, queue_full, insufficient_minute_balance, limit_exceeded
429rate_limited (honor Retry-After), daily_cap_exceeded
500internal_error, not_configured
502recording_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:
{
  "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:
{
  "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).