DentareDocs

Patients API

Build integrations with Dentare's REST API - authentication, endpoints, and runnable curl examples for every operation.

What you'll build

Dentare exposes a REST API for managing patients programmatically. Use it to import a legacy patient list, sync patients from another system, or build custom integrations on top of your clinic data.

This guide takes you from zero to a working integration: generate a token, run your first authenticated request, then walk through the full CRUD lifecycle with copy-paste curl examples for every operation.

Before you start

You'll need:

  • A Dentare account with admin access
  • A terminal with curl installed, or Postman
  • Basic familiarity with HTTP requests and JSON

Authentication

How it works

Every request to the API must include a Bearer token in the Authorization header. Tokens are scoped to your account - they can only read or modify data belonging to your clinic, not other Dentare accounts.

Tokens never expire automatically. You can revoke them at any time from the dashboard.

Generate an API token

Open the API Tokens page

Log in to Dentare as an account admin and go to API Tokens.

Create a token

Click Create an API Token, give it a descriptive name (e.g., "Patient sync script"), and click Create.

Copy the token immediately

The full token is displayed once. Copy it now and store it somewhere secure - you cannot view it again.

Treat your API token like a password. Never commit it to source control or paste it in a public channel. Anyone with the token can read and modify your clinic's data.

Make your first authenticated request

The simplest "ping" you can run is to fetch your own user profile from /api/v1/me.json. If your token works, you'll get a 200 response with your account info.

curl https://dentare.io/api/v1/me.json \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Expected response:

{
  "id": 1,
  "name": "Your Name",
  "avatar_url": "https://dentare.io/users/1/avatar.png",
  "sgid": "BAh7CEkiCGdpZAY6BkVUS..."
}

A 200 with this payload confirms the token works. The id and name fields are the most useful for sanity checks. sgid is a signed global ID used internally and is safe to ignore.

Common authentication errors

401 Unauthorized means the token wasn't accepted. Three things to check:

  • The Authorization header uses the literal Bearer prefix (note the trailing space)
  • The token was copied without leading or trailing whitespace
  • The token hasn't been revoked from the dashboard

Revoke a token

If a token leaks, ends up in the wrong hands, or is no longer needed, revoke it from the dashboard. The token stops working immediately.

Open the API Tokens page

Log in to Dentare as an account admin and go to API Tokens.

Open the token

Click the name of the token you want to revoke.

Click Revoke

The token is deleted on the spot and cannot be re-enabled. Any script or integration using it will start receiving 401 Unauthorized on the next request.

Base URL and headers

Base URLhttps://dentare.io/api/v1
AuthenticationAuthorization: Bearer YOUR_API_TOKEN
Content type (write requests)Content-Type: application/json
Accept (recommended)Accept: application/json

Using Postman

Save yourself from pasting the token into every request:

  1. Create a new Postman environment named "Dentare"
  2. Add a variable: baseUrl = https://dentare.io/api/v1
  3. Add a variable: apiToken = your token (mark as secret type)
  4. In every request, use {{baseUrl}} for the URL and Bearer {{apiToken}} for the Authorization header
  5. Save the collection as "Dentare API" so your team can fork it

This pattern works whether you eventually publish a hosted collection or not.

List patients

GET /api/v1/patients

Returns a paginated list of patients in your account. Active patients only by default.

Basic example

curl https://dentare.io/api/v1/patients \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
GET {{baseUrl}}/patients
Authorization: Bearer {{apiToken}}
Accept: application/json

Response:

{
  "data": [
    {
      "id": 4821,
      "first_name": "Maria",
      "last_name": "Hoxha",
      "display_name": "Maria Hoxha",
      "email": "[email protected]",
      "phone": "+355691234567",
      "phone_e164": "+355691234567",
      "locale": "sq",
      "is_active": true,
      "created_at": "2026-04-25T14:32:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 137
  }
}

Pagination

Pass page and per_page to walk through results. Default page size is 20, maximum is 100.

curl "https://dentare.io/api/v1/patients?page=2&per_page=50" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

The q parameter searches across name, email, phone, and personal number.

curl "https://dentare.io/api/v1/patients?q=hoxha" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Filtering

Pass any of these to narrow the result set. All are optional and combine with AND.

ParameterMatches
emailExact email match
phonePartial phone number match
personal_noNational ID / EMBG
external_idID from another system
legacy_idID from a previous Dentare migration
is_activetrue or false (defaults to true only)
curl "https://dentare.io/api/v1/[email protected]" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
By default, only active patients are returned. Pass is_active=false to include archived patients.

Supports ?lang=en|sq|mk to localize display_name in the response. See Common parameters.

Get a single patient

GET /api/v1/patients/:id

Returns the full record for one patient. Pass fields (comma-separated) to include extra data not returned by default.

Example

curl https://dentare.io/api/v1/patients/4821 \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response:

{
  "data": {
    "id": 4821,
    "first_name": "Maria",
    "last_name": "Hoxha",
    "display_name": "Maria Hoxha",
    "display_first_name": "Maria",
    "display_last_name": "Hoxha",
    "email": "[email protected]",
    "phone": "+355691234567",
    "phone_e164": "+355691234567",
    "phone_country": "AL",
    "birthdate": "1990-05-15",
    "gender": "female",
    "personal_no": null,
    "external_id": null,
    "legacy_id": null,
    "locale": "sq",
    "is_active": true,
    "created_at": "2026-04-25T14:32:00Z",
    "updated_at": "2026-04-25T14:32:00Z"
  }
}

With expansion fields

The default response excludes medical, emergency contact, insurance, and VIP fields to keep payloads small. Request them explicitly with fields:

curl "https://dentare.io/api/v1/patients/4821?fields=allergies,medical_conditions,insurance_provider" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Supports ?lang=en|sq|mk to localize display_name, display_first_name, and display_last_name. See Common parameters.

Patient not found

{
  "error": "Not Found"
}

A 404 response means the patient does not exist, or it belongs to a different account. The API never leaks data across accounts.

Create a patient

POST /api/v1/patients

Required fields

Only two:

  • first_name (string)
  • last_name (string)

Everything else is optional.

Minimal example

curl -X POST https://dentare.io/api/v1/patients \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "first_name": "Maria",
    "last_name": "Hoxha"
  }'
POST {{baseUrl}}/patients
Authorization: Bearer {{apiToken}}
Content-Type: application/json
Accept: application/json

Body (raw, JSON):
{
  "first_name": "Maria",
  "last_name": "Hoxha"
}

Response (201 Created):

{
  "data": {
    "id": 4821,
    "first_name": "Maria",
    "last_name": "Hoxha",
    "display_name": "Maria Hoxha",
    "email": null,
    "phone": null,
    "phone_e164": null,
    "is_active": true,
    "created_at": "2026-04-25T14:32:00Z",
    "updated_at": "2026-04-25T14:32:00Z"
  }
}

Full example

A more realistic create with contact, demographic, and locale fields:

curl -X POST https://dentare.io/api/v1/patients \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "first_name": "Maria",
    "last_name": "Hoxha",
    "email": "[email protected]",
    "phone": "+355691234567",
    "phone_country": "AL",
    "birthdate": "1990-05-15",
    "gender": "female",
    "personal_no": "I90515123A",
    "locale": "sq",
    "external_id": "ext_2841"
  }'

All available fields

FieldTypeNotes
first_namestringRequired
last_namestringRequired
emailstring
phonestringAuto-normalized to E.164
phone_countrystringTwo-letter code (MK, XK, AL) - helps phone normalization
birthdatestringISO 8601 date: YYYY-MM-DD
genderstringmale, female, or other
personal_nostringNational ID / EMBG
localestringen, sq, or mk - language used for patient-facing messages
external_idstringYour reference ID from another system
blood_typestring
allergiesstringFree text
medical_conditionsstringFree text
emergency_contact_namestring
emergency_contact_relationshipstring
emergency_contact_phonestring
insurance_providerstring
insurance_idstringInsurance policy number
is_activebooleanDefaults to true
preferred_doctor_idintegerID of the patient's preferred doctor

Validation errors

If a required field is missing or a value is malformed, the API returns 422 Unprocessable Entity with a per-field breakdown:

{
  "error": "Validation failed",
  "errors": {
    "first_name": ["can't be blank"],
    "email": ["is invalid"]
  }
}
Phone numbers are normalized to E.164 international format (e.g., +355691234567). The normalized value is returned in phone_e164. Pass phone_country if your input numbers don't include a country code.

Update a patient

PATCH /api/v1/patients/:id

PATCH is partial: send only the fields you want to change. Anything you omit stays as it is.

Example

curl -X PATCH https://dentare.io/api/v1/patients/4821 \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "email": "[email protected]",
    "phone": "+355692223344"
  }'

Response (200 OK):

{
  "data": {
    "id": 4821,
    "first_name": "Maria",
    "last_name": "Hoxha",
    "email": "[email protected]",
    "phone": "+355692223344",
    "phone_e164": "+355692223344",
    "updated_at": "2026-04-25T15:10:00Z"
  }
}

Why PATCH vs PUT

PATCH only updates the fields in the request body. To clear a field, send null explicitly. The Dentare API does not support PUT (full-replace) on patients - use PATCH for everything.

Archive a patient

The Patients API does not expose a DELETE endpoint. To remove a patient from active lists, archive them by setting is_active to false:

curl -X PATCH https://dentare.io/api/v1/patients/4821 \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'

Archived patients are excluded from list responses by default. Pass is_active=false to see them, or omit the filter to see only active patients.

Common parameters

These four patterns apply to every GET endpoint:

  • Pagination - ?page=N&per_page=M (default 20, max 100). Collection endpoints only.
  • Filtering - any combination of email, phone, personal_no, external_id, legacy_id, is_active, plus the search param q. Collection endpoints only.
  • Expansion - ?fields=field1,field2,... to include fields excluded from the default response.
  • Localized responses - ?lang=en|sq|mk to control the language of computed fields like display_name. Defaults to the patient's stored locale if set, otherwise English. Available on all read endpoints.

For the patient detail endpoint, available expansion fields are:

allergies, medical_conditions, blood_type, emergency_contact_name, emergency_contact_relationship, emergency_contact_phone, insurance_provider, insurance_id, vip, vip_start_date, vip_expiration_date, vip_notes, preferences, medical_issues

Error reference

HTTP status codes

CodeMeaningWhen you'll see it
200OKSuccessful read or update
201CreatedSuccessful create
401UnauthorizedMissing, malformed, or revoked token
404Not FoundPatient ID doesn't exist or belongs to another account
422Unprocessable EntityValidation failed - check the errors object

Error response shape

{
  "error": "Validation failed",
  "errors": {
    "first_name": ["can't be blank"],
    "phone": ["is invalid"]
  }
}

error is always a human-readable string. errors (plural, lowercase) is present on 422 responses and maps each invalid field to an array of error messages.

Rate limits

The Patients API does not enforce per-token rate limits today. Be a good citizen: avoid tight loops, paginate large reads, and back off if you start seeing 429 responses (which would come from the platform edge, not the API itself).

Troubleshooting

401 Unauthorized on every request

Check that your Authorization header uses the correct format: Bearer YOUR_API_TOKEN. The literal word Bearer is required, with one space before the token. If the format is right, confirm the token has not been revoked from the dashboard.

404 Not Found when accessing a patient

The patient either does not exist or belongs to a different account. API requests are scoped to your account: you cannot read or modify patients from another clinic, even if you guess a valid ID.

422 Validation error on create

At minimum, first_name and last_name are required. The errors object in the response maps each invalid field to an array of messages. Read it field by field.

Phone number appears different after create or update

Dentare normalizes phone numbers to E.164 international format (e.g. +38970123456). The normalized value is returned in the phone_e164 field. Pass phone_country in the request body to help normalization when your input numbers do not include a country code.

Search returns no results

The q parameter searches across name, email, phone, and personal number. Make sure the search term matches at least one of those fields. Active patients only by default; pass is_active=false to include archived ones.

Special characters appear garbled

Ensure your request uses UTF-8 encoding. Set the Content-Type: application/json; charset=utf-8 header on write requests.

Postman

The variable-based pattern in the Postman section above is the recommended starting point: copy a single example, save it to a "Dentare API" collection, and reuse {{baseUrl}} and {{apiToken}} across every request.

What's next

On this page