The Dentally NexGen API authenticates requests using OAuth 2.0 bearer tokens. Integrations call the API from a server (backend jobs, services, middleware) — there is no browser-based or end-user login flow for API access.
This page applies whether you are an approved partner, a practice connecting your own systems, or evaluating an integration before you apply.
API credentials are not self-service. How you obtain them depends on who you are:
| Who you are | How to get credentials |
|---|---|
| Partner (building a product for multiple practices) | Apply through the Dentally partnership program. After approval and onboarding, Dentally issues a client_id and client_secret for your integration. |
| Practice (connecting Dentally to systems you operate) | Enrol in the Practice Integration Program (PIP). Dentally issues credentials scoped to your practice as part of that process. |
Store client_id and client_secret as secrets. Do not embed them in client-side code, mobile apps, or public repositories.
A product is a single service or product managed by a partner. Partners may have multiple products. When a partner is onboarded, client credentials are issued which are used to access the NexGen APIs. Each product has a single set of these credentials, consisting of a client id and secret. Each Partner product can have a different set of API scopes and permissions. This is to govern data access ensuring only necessary data is shared.
Subscriptions are what authorizes a partner to access a practice's data, and are managed per practice. The practice grants permission for a partner to access their data. Access is then provisioned by the Dentally Partnerships team.
Request an access token with the OAuth 2.0 client credentials grant. POST to the token endpoint on the same API host you call for data:
POST {api_base}/v2/oauth/tokenFor UK sandbox, {api_base} is https://api.sandbox.dentally.co, so the token URL is https://api.sandbox.dentally.co/v2/oauth/token. Use the base URL for the environment and region you will call (see Audience).
The audience must match the API identifier for that environment and region:
Example request (UK sandbox):
curl --request POST \
--url 'https://api.sandbox.dentally.co/v2/oauth/token' \
--header 'Content-Type: application/json' \
--data '{
"grant_type": "client_credentials",
"client_id": "<client_id>",
"client_secret": "<client_secret>",
"audience": "https://api.sandbox.dentally.co"
}'| Field | Notes |
|---|---|
grant_type | Must be client_credentials. |
client_id | Issued during onboarding. |
client_secret | Issued during onboarding. |
audience | Matches the API identifier for the environment and region you will call. See Audience. |
| Environment | Region | Audience |
|---|---|---|
| Sandbox | APAC | https://api.apac-sandbox.apac.dentally.com |
| Sandbox | UK | https://api.sandbox.dentally.co |
| Production | APAC | https://api.apac.dentally.com |
| Production | UK | https://api.dentally.co |
A token issued for one audience is not accepted by another. Call the API host that matches the audience you requested.
Access tokens are JWTs. The iss (issuer) claim identifies Dentally's authorization service. The token endpoint lives into each API host, but the issuer values do not:
| API environments | Issuer (iss) |
|---|---|
| Sandbox and Production | https://auth.api.dentally.com |
If you validate JWTs locally (signature, issuer, or audience), keep using these issuer URLs. Do not derive iss from the host you POST to for /v2/oauth/token.
A successful response includes a short-lived JWT access token and the scopes granted to that token:
{
"access_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "appointment:read patient:write"
}| Field | Notes |
|---|---|
access_token | JWT to send as the bearer token on API requests. |
token_type | Always Bearer. |
expires_in | Lifetime of the access token in seconds. |
scope | Space-separated list of scopes granted to this token. |
Access tokens are valid for 24 hours (86,400 seconds). The expires_in field in the response reflects this lifetime. Request a new token before the current one expires — expired tokens are rejected with 401 Unauthorized (see Failure modes).
Cache the token until it expires (or until shortly before expiry) and reuse it across requests. Requesting a new token on every API call is unnecessary and may hit rate limits. See Rate limiting for API request limits and how to handle 429 responses.
Send the access token on every API call:
Authorization: Bearer <jwt>
Dentally-API-Version: 2026-01-01
X-Practice-Id: <practice-uuid>| Header | Required | Notes |
|---|---|---|
Authorization | yes | Bearer scheme. Value is the JWT from the token endpoint. |
Dentally-API-Version | recommended | Pin a dated revision. See Versioning. |
X-Practice-Id | yes | UUID of the practice the request acts on. |
The token response includes a scope field listing the permissions granted to your credentials. Each operation in the API reference also lists the scopes required to call it. Scopes reflect what your integration is approved to do (for example appointment:read or patient:write). If your token lacks the scope required by an operation, the API responds with 403 Forbidden.
The set of scopes available to you is fixed at onboarding. If you need additional access, contact the APIs & Partnerships team.
Dentally is multi-tenant: every API request is scoped to a single practice via X-Practice-Id. Send the UUID of the practice you are acting on. Each request targets one practice at a time.
Your integration must only call the API for practices you are authorised to access (for example practices that have subscribed to your product, or your own practice under PIP). A valid token alone is not enough — the gateway also checks that your integration may access the practice named in the header.
Returned when credentials are missing, malformed, or expired — for example no Authorization header, an invalid JWT, or an expired access token.
{
"type": "https://httpstatuses.io/401",
"title": "Unauthorized",
"status": 401,
"detail": "Missing or invalid bearer token.",
"instance": "/v2/sites",
"traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}Obtain a fresh token from the token endpoint and retry.
Returned when the token is valid but the request is not permitted — for example your integration lacks the required scope, or you are not authorised to access the practice given in X-Practice-Id.
Do not retry blindly. Confirm the practice ID, your approved scopes, and that the practice has granted your integration access.
See Errors for the full list of error responses and handling guidance.