> ## Documentation Index
> Fetch the complete documentation index at: https://build.practicehub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Clinical notes

> What a clinical note is on the API, how to find a template, and how to read and create notes

`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.

<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.
</Info>

## 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:

```bash theme={null}
curl -s "$BASE/form_templates?form_type=practitioner" -H "Authorization: Bearer $KEY"
```

| Field | Notes |
| - | - |
| `id` | What you send as `clinical_notes.form_id` |
| `name` | The template's name, as shown in PracticeHub's Forms settings |
| `form_type` | `practitioner`, `scribe` or `patient` — only `practitioner` and `scribe` templates can be used for `clinical_notes` |
| `status` | Only an `active` template can be written against |
| `schema` | The field structure — this is what tells you the field names to use in `data` |
| `is_system` | Whether PracticeHub itself created the template |

`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

```bash theme={null}
curl -s "$BASE/clinical_notes?patient_id=340" -H "Authorization: Bearer $KEY"
```

`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:

| Param | Notes |
| - | - |
| `patient_id` | Only this patient's notes |
| `state` | `draft` or `final` |
| `note_type` | `practitioner` (typed notes) or `scribe` (AI Scribe notes) |
| `updated_since` | `YYYY-MM-DDTHH:MM:SS`, clinic timezone — for incremental sync, same pattern as everywhere else on the API |
| `page` / `page_size` | Offset pagination, `page_size` up to 100 (default 25) |

`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`).

<Note>
  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.
</Note>

## Creating

`patient_id`, `form_id` and `data` are all required — there's no fallback template, so pick one with `GET /form_templates` first (above):

```bash theme={null}
curl -X POST $BASE/clinical_notes \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "patient_id": 340,
    "form_id": 12,
    "appointment_id": 6350,
    "state": "final",
    "note_type": "practitioner",
    "appt_date": "2026-08-19",
    "data": {
      "subjective": "Lower back pain, improving",
      "objective": "Reduced tenderness L4/L5",
      "assessment": "Continued improvement",
      "plan": "Weekly sessions for 2 more weeks"
    }
  }'
```

| Field | Notes |
| - | - |
| `patient_id` | Required |
| `form_id` | Required. An active `practitioner` or `scribe` template of the account — find one with `GET /form_templates` |
| `data` | Required. Answers keyed by the template schema's field names (or field ids — notes converted from before 2.0 key by id). A key the schema doesn't define is refused (`422 unknown_field`) rather than silently dropped |
| `appointment_id` | Optional — must belong to `patient_id` (`422 appointment_not_found` / `appointment_patient_mismatch` otherwise) |
| `state` | `draft` or `final`, defaults to `final`. `final` stamps `finalized_at` / `finalized_by` |
| `note_type` | `practitioner` or `scribe`; defaults to the template's own type |
| `appt_date` | `YYYY-MM-DD`, the date of the documented visit |
| `created_at` | Historical-import override — see below |
| `metadata` | Same [metadata](/guides/metadata) contract as every other resource |

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](#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:

```bash theme={null}
curl -X POST $BASE/clinical_notes \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: import-note-N-77410" \
  -d '{
    "patient_id": 340,
    "form_id": 12,
    "state": "final",
    "created_at": "2024-03-14T11:05:00Z",
    "data": { "subjective": "Lower back pain, 6/10, since lifting at work" },
    "metadata": { "legacy_id": "N-77410" }
  }'
```

Tag every imported note with its old system's id in `metadata` — the same [legacy-ID pattern](/guides/data-migration#the-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](mailto:help@practicehub.io) — see [Resources & roadmap](/resources).

## 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.

<Note>
  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](/mcp/administration) for the toggle that governs it.
</Note>

See [Errors](/guides/errors) for the full code catalogue, and [Administering AI connectors](/mcp/administration) for how an owner grants or removes the note permission a connector needs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.