clinical_notes reads and writes the same notes the in-app note editor does — there’s no parallel “API note” concept. A note is the set of answers a note template collected, frozen against the exact schema it was written with, plus the note’s clinical context — patient, appointment, author, state, revision info.
Both endpoints go through
ClinicalNoteService, the same authority the in-app note editor writes through — the patient must exist, a linked appointment must be the patient’s own, and every answer key must belong to the template’s schema. There’s no looser API path.Finding a note template first
Unlike some resources, a clinical note is never created against an implicit default — every create names aform_id, one of the account’s own note templates. form_templates is the read-only resource for finding one:
GET /form_templates/{id} reads one template’s full schema. Only note templates built in PracticeHub 2.0 appear here.
If the account doesn’t already have a suitable template, create (or ask an account owner to create) one in PracticeHub → Settings → Forms before importing or integrating — the API does not create templates. Most practices already have at least one SOAP-style practitioner template; check before assuming you need a new one.
Reading
GET /clinical_notes returns current revisions only, newest first — a note that’s been edited appears once, as its latest content, never as a stack of old versions. Filter with:
GET /clinical_notes/{id} reads one note the same way. Both return the same shape: data (raw answers, keyed by the form schema’s field names), formatted_fields (human-readable renderings resolved against the schema snapshot the note was written with — {key, label, type, value, section}), formatted_soap (Subjective/Objective/Assessment/Plan, when the note is SOAP-structured), practitioner, note_author, appointment, state, note_type, canvases, files, patient_id, metadata, and revision fields (revision_number, is_current — always true here, previous_revision_id, revised_by_uid, finalized_at, finalized_by_uid).
Notes written before PracticeHub 2.0 are invisible to the API until they’re converted in the app — once converted, a note reads exactly like one created through the API. There’s no separate historical-note shape to handle.
Creating
patient_id, form_id and data are all required — there’s no fallback template, so pick one with GET /form_templates first (above):
A create always starts a new note as revision 1 — the API doesn’t write revisions onto an existing note (see Out of scope for v1).
Historical imports: keeping the note’s original date
Sendcreated_at to back-date the note so it keeps its place in the patient’s chronology instead of appearing as if written today. For a final note this also back-dates finalized_at, mirroring how PracticeHub itself treats imported history:
metadata — the same legacy-ID pattern used across the rest of the API lets you check for a note before re-creating it (GET /clinical_notes?metadata[legacy_id]=eq:N-77410) and trace any note back to its source. Send an Idempotency-Key too, for transport-level retry safety on top of that.
If your source data is a single block of free text per visit rather than separated SOAP fields, use (or create) a template with a single free-text field and send everything under that one field name.
Out of scope for v1
The API is deliberately narrower than the in-app note editor. None of the following are reachable throughclinical_notes yet:
- Canvases, signatures and drawn annotations — a note’s
canvasesfield is readable, but there’s no way to write one through the API. - AI Scribe audio —
scribe_attachment(transcription and summary) is readable on ascribenote, but the API has no route to submit audio or trigger a transcription. - Editing or deleting a note through the API — there is no
PATCHorDELETEonclinical_notes. A note, once created, is fixed from the API’s side. - Writing a revision onto an existing note — every create starts a fresh revision-1 chain; there’s no way to supersede a note you (or the app) already wrote. Revision history is readable (
revision_number,previous_revision_id,revised_by_uid) but not writable. - Creating a note template —
form_templatesis read-only over the API; build templates in Settings → Forms.
Access
- Integration keys read and write notes by their own scope — a read-only key gets
403 key_read_onlyonPOST /clinical_notes, exactly as on every other resource. - Connected assistants (OAuth connectors) follow the member’s own note permission. A connector can list and fetch notes when the approving team member’s PracticeHub role carries Patient visit notes access — the same permission that shows or hides the notes tab in the app — and the usual own-patients ringfence narrows what they see if that’s how their role is scoped. Without the permission, reads refuse with
403 permission_denied. Connectors cannot create notes during the limited rollout — the MCP server registers no write tools; when write access is switched on, the same permission will governPOST /clinical_notestoo. - Once connector writes arrive, a role restricted to draft-only note access will create notes only as
draft— sendingstate: "final"(or omittingstate, which defaults tofinal) is refused for that role. Sendstate: "draft"explicitly.
Whether a connected assistant should be creating clinical documentation, once write access arrives, is a judgement call for the practice, not a technical one — the API enforces the same permission a person needs, nothing more. See Administering AI connectors for the toggle that governs it.