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

Send created_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:
Tag every imported note with its old system’s id in 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 through clinical_notes yet:
  • Canvases, signatures and drawn annotations — a note’s canvases field is readable, but there’s no way to write one through the API.
  • AI Scribe audio — scribe_attachment (transcription and summary) is readable on a scribe note, but the API has no route to submit audio or trigger a transcription.
  • Editing or deleting a note through the API — there is no PATCH or DELETE on clinical_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_templates is read-only over the API; build templates in Settings → Forms.
If a use case needs any of these, tell us — see Resources & roadmap.

Access

  • Integration keys read and write notes by their own scope — a read-only key gets 403 key_read_only on POST /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 govern POST /clinical_notes too.
  • Once connector writes arrive, a role restricted to draft-only note access will create notes only as draft — sending state: "final" (or omitting state, which defaults to final) is refused for that role. Send state: "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.
See Errors for the full code catalogue, and Administering AI connectors for how an owner grants or removes the note permission a connector needs.