Guide: Read a session note

A clinician runs a session in a PlaySpace surface your application hosts. They write it up afterwards — or let PlaySpace draft the note from the session audio — and your own system can then file that note against the encounter you booked, without anyone re-keying it.

This guide is the read path, and it is a Level 4 integration: server to server, no frame. It covers what you get back, how to ask for the verbatim session transcript, how to wait for a note that is still being written, and the things this surface deliberately does not return.

This API is read-only on notes, and permanently so. You cannot create, edit, lock, unlock or delete a note from your server, and there is no plan to add it. A clinical note is the clinician's own record of a patient, and your application is not the clinician.

There is one place a note can be changed, and the clinician is the one doing it: inside the framed PlaySpace workspace, where a seat minted note:write alongside note:read lets that clinician edit, sign and unlock their own notes in your product, exactly as they would in PlaySpace. That never reaches this API — no endpoint here gains a write verb from it — and nothing anywhere creates or deletes a note through a partner integration.


Before you start

  • An approved partner organisation with credentials for the environment you are working in. See Authentication.
  • A delegated token. A note belongs to one clinician, so every call here acts as a specific practitioner. An organisation-wide token has nobody to scope to and is refused with 403 — it is not silently widened, and there is no organisation-wide view of clinical notes.
  • Somewhere to put clinical content. A note body is protected health information. Treat what comes back the way you treat the rest of a patient record: store it under the same controls, and keep it out of logs, URLs, analytics and error reports. See Security and compliance.

Step 1 — list the clinician's notes

GET /v1/partner/notes
Authorization: Bearer <delegated token>
{
  "data": [
    {
      "id": "3f5a1c22-9d84-4b17-a3f2-6c1e0b7d4a91",
      "title": "Session note",
      "description": null,
      "locked": false,
      "created_at": "2026-09-02T14:05:11.000Z",
      "updated_at": "2026-09-02T14:41:02.000Z",
      "partner_appointment_id": "b2d7e4f0-1a3c-4e56-8b90-2c4d6e8f0a12",
      "partner_patient_id": "7c9e1b34-5d20-4a68-9f31-8e0d2a4c6b58",
      "partner_practitioner_id": "d41f8a6c-2b57-4390-8e12-5a7c9b0d3e64"
    }
  ],
  "meta": {
    "request_id": "8f1c9b40-6c1e-4a2f-9a44-0d5e2b7c3a11",
    "generated_at": "2026-09-02T14:42:00.000Z",
    "pagination": { "next_cursor": null, "limit": 25, "has_more": false }
  }
}

Notes come back newest first, cursor-paginated like every other list — follow meta.pagination.next_cursor until it is null. Pagination in general: Conventions.

The three identifiers are yours, not ours. partner_appointment_id, partner_patient_id and partner_practitioner_id are the id values on your own appointment, patient and practitioner records, so a note files itself against the encounter you booked with no lookup table on your side.

They can all be null, and that does not mean the note is orphaned. It means the note has no appointment of yours behind it — a clinician who starts an ad-hoc recording, rather than working from an appointment you booked through this API, produces a real note with no appointment at all. Expect nulls and store the note anyway.

Read the patient record for identity. A patient's name, date of birth and contact details are never repeated on a note. You already hold the record that partner_patient_id addresses; restating it here would widen the disclosure for nothing.


Step 2 — read one note's body

GET /v1/partner/notes/{id}
Authorization: Bearer <delegated token>

The single-note response is the same object plus the written record itself:

{
  "data": {
    "id": "3f5a1c22-9d84-4b17-a3f2-6c1e0b7d4a91",
    "title": "Session note",
    "content": "Presenting concerns…",
    "version_number": 3,
    "locked": true,
    "…": "…"
  },
  "meta": {
    "request_id": "8f1c9b40-6c1e-4a2f-9a44-0d5e2b7c3a11",
    "generated_at": "2026-09-02T14:42:00.000Z"
  }
}

content is the body as the clinician most recently saved it, and version_number says which revision that is — it rises each time they save. Both are null or empty on a note that has never been written to, which is the normal state of a note whose transcription is still running.

locked is a clinical act, not a permissions flag. A locked note is the finalised record of that session; the clinician has decided it is done.

A note belonging to a different clinician is indistinguishable from one that never existed — both answer 404 — so the response cannot be used to probe what lies outside your delegated token's reach.

This is also the only place the verbatim session transcript is available, by adding include=transcript — see Asking for the session transcript below.


Bringing the bodies back with the list

Add include=content and every note in the page carries its content and version_number:

GET /v1/partner/notes?include=content&limit=50

It is one request either way — never one call per note. It is off by default on purpose: a page of notes returns titles and timing so you can decide what to open, and a page of full clinical bodies is a materially larger release of information than a page of titles. Ask for it when you are synchronising, not when you are rendering a list.


Asking for the session transcript

When PlaySpace drafts a note from session audio, it keeps the verbatim, speaker-diarised record of what was said in the room. You can read it — on the single-note endpoint, and only when you ask:

GET /v1/partner/notes/{id}?include=transcript
Authorization: Bearer <delegated token>
{
  "data": {
    "id": "3f5a1c22-9d84-4b17-a3f2-6c1e0b7d4a91",
    "title": "Session note",
    "content": "Presenting concerns…",
    "version_number": 3,
    "transcript": [
      {
        "speaker": "Speaker 1",
        "text": "Do you want to show me what you built?",
        "start_seconds": 12.4,
        "end_seconds": 15.1
      },
      {
        "speaker": "Speaker 2",
        "text": "It's a house for the dog.",
        "start_seconds": 15.6,
        "end_seconds": 17.9
      }
    ],
    "…": "…"
  },
  "meta": { "…": "…" }
}

Entries arrive in order, one per speaker turn — every consecutive word from the same speaker, joined into a single stretch of speech — so the array reads as a conversation and needs no re-assembly on your side:

Field What it is
speaker Who said it, as the diarisation labelled them — a name where the recording carried one, otherwise the raw speaker identifier the transcription assigned, and null when the turn could not be attributed to anybody. Labels are stable within a transcript and mean nothing across two. A change of speaker is what ends one turn and starts the next.
text What was said, verbatim — the whole turn, not a single word.
start_seconds Offset from the start of the recording, in seconds, at which the turn begins.
end_seconds Offset at which it ends. Equal to start_seconds when the source gave no end time.

Three responses, and your code has to tell them apart

  • The key is absent. You did not ask for it. This is the shape of every note in a list, and of every single-note read without the parameter. Absence says nothing about whether a transcript exists.
  • transcript is null. You asked, and this note has none. The session was never recorded, or the note was typed by hand, or the transcription has not finished yet — those are indistinguishable from here, and none of them is an error. Treat it the way you treat an empty content: poll, do not retry as a failure.
  • transcript is an array. You asked, and the recording is there.

Why it is one note at a time

include=transcript does not exist on GET /v1/partner/notes, at any page size, and that is a decision rather than an omission.

The first reason is size. The longest transcript measured on a PlaySpace database holds around a megabyte of verbatim speech — an hour of two people talking, every word of it. Grouping that speech into turns removes rows, not words: the published array is hundreds of entries rather than tens of thousands, but it carries the same recording. A page of 25 notes each attaching one of those is still tens of megabytes returned from a call you made to render a list, and no pagination setting makes that a sane default.

The second reason is disclosure. A page of titles tells you which sessions exist. A page of transcripts hands you every word spoken in all of them. Those are not the same act, so the API makes you perform the second one deliberately, one note at a time, instead of as a side effect of listing.

There is no separate permission for it — it rides practitioners:read like everything else on this surface, and no per-organisation setting gates it. The control is the shape of the request, which means the decision about whether your product ever reads a transcript is one you make in your own code. Make it explicitly.

The derived note and the verbatim record are not the same disclosure

content is what a trained clinician chose to write down: interpreted, edited, and intended to be read by other professionals. transcript is everything that was said, including what the clinician judged not to record — a child's exact words, digressions, distress, the parts of the hour that never became a clinical finding.

Both are protected health information and both belong under the same controls once they are on your side. But they are not interchangeable, and reading a transcript because it is available is not the same as needing it. Ask for one when a workflow genuinely requires the source — a clinician reviewing their own session, or a supervision or quality process they have agreed to — and take the clinician's note otherwise.

Whatever you do with it, do not log it, do not send it through an analytics or error-reporting pipeline, and never put any part of it in a URL. It is the most sensitive payload this API returns.


Waiting for a note that PlaySpace is still writing

A note drafted from session audio is produced asynchronously, and the work outlives the browser tab: the recording is finalised, transcribed and structured after the session ends. Nothing about that is instant, and a long session takes longer.

There is no change feed and no webhook for it. Poll GET /v1/partner/notes for the clinician you care about and watch for a new id, or for updated_at moving on a note you already hold. A note whose content is still empty is a note that is still being written, not a failed one.


What this surface will never return

A patient's identity, restated. As above: identifiers only.

Any write. No create, no update, no lock, no delete. There is no soft-delete endpoint here either, so nothing about a note is reversible from your side because nothing about it is changeable from your side. A clinician editing or signing a note in the framed workspace changes what this endpoint returns on the next read; it does not give your server a way to make that change itself.


Treat the title as clinical content

title looks like a label and is not one. It is usually taken from the appointment the note belongs to, but a note written without an appointment takes its title from the opening words of the note body — so a title can be a sentence about a patient.

Render it. Do not log it, do not put it in a URL or a query string, do not send it to an analytics or error-reporting service, and do not use it as a display name anywhere a note body would not also be acceptable. This is the same rule the rest of the API follows for identifiers versus values, stated in Conventions.


When something is refused

Every failure is a problem document. The ones you will actually meet here:

Status What happened
401 The token is missing, expired or not for this environment.
403 The token is organisation-wide. Request a delegated token for the clinician whose notes you want.
404 No note with that id belongs to the clinician your token acts as.
422 A malformed id, or a limit outside 1–100.

Full problem-type vocabulary and retry advice: Errors.


Next