Skip to main content
Every resource is a uniform collection under https://{clinic}.{region}.practicehub.io/v3/api:

Filters

Filter with ?attribute=operator:value. Any filterable attribute (see GET /schema/{resource}) accepts any operator; repeat an attribute to AND several conditions.
Dates and times are in the clinic’s timezone, formatted YYYY-MM-DD or YYYY-MM-DD HH:MM:SS. Attributes the API speaks in words (patients.sex: male, female, other) are filtered by those words — sex=eq:female, sex=in:male,female — never by a stored code; a word outside the set is a 422.
Filtering on an attribute the resource does not expose, or an unsortable attribute, is a 422 with the attribute named in errors — nothing is silently ignored.

Metadata filters

Your own identifiers stored in metadata are filterable on every resource with metadata[key]=operator:value — see Metadata.

Sorting

?sort=attr sorts ascending; ?sort=attr:desc descending; ?sort=last_name,first_name:asc applies one direction to several attributes. GET /schema/{resource} marks which attributes are sortable.

Pagination

Lists are paginated with page (from 1) and page_size (default and maximum 100). Every list response carries navigation links and counts:
Follow links.next until it is null; the links preserve your filters and sort. Ordering is stable — the record id is always applied as a final tiebreaker — so pages never repeat or skip rows for a fixed dataset. Records created or deleted between pages can still shift offset pages, and deep pages get slower on large tables — for syncs and exports use a cursor instead.

Cursor pagination

Add cursor= (empty) to a list request to walk it by cursor. Each page’s links.next carries an opaque token that encodes where the last row sat in the sort order; the server resumes strictly after it (an index seek, not a skip), so every page costs the same however deep you are, and a record created or deleted elsewhere in the set cannot shift or duplicate what you have already seen. Same filters, sort and page_size apply.
Follow links.next until it is null. Cursors are forward-only, carry no total, and are bound to the query they were issued for — reusing one with different filters, sort or page_size is a 422.

Discovering the model

GET /schema/{resource} is the contract for that resource:
The API Reference is generated from the same source, so both always agree.

Response shape

  • Attributes are snake_case; ids are integers; empty values are null.
  • Every record includes id and a metadata object.
  • Only the attributes listed in GET /schema/{resource} are ever returned — new attributes may be added over time, existing ones are not renamed or removed within the current version.

Deleted records

Records deleted in PracticeHub disappear from their resource. GET /deleted_entities lists what was deleted and when, so a sync can remove its copies — filter with created=gte:{last sync} and read entity_type / entity_id.