{
  "openapi": "3.1.0",
  "info": {
    "title": "PlaySpace Partner API",
    "version": "0.1.0",
    "description": "Partner-scoped REST API for managing clinics, practitioners, patients, and appointments. All endpoints live under the base path `/v1/partner`. Each partner organization sees only its own data.\n\n## Authentication\n\nAuthenticate with an OAuth2 client-credentials (machine-to-machine) access token issued by Auth0 for your partner organization. Send it on every request as `Authorization: Bearer <token>`. Verify your token and connectivity with `GET /v1/partner/health` before calling any business endpoint. Scopes follow a `resource:verb` naming convention — for example `clinics:read` or `patients:write` — and each operation documents the exact scope it requires.\n\n## Trying these endpoints from the console\n\nThe API hosts serve this reference with a live console at `/docs` (and `/docs/practitioner` for the practitioner tier). To make a call from it:\n\n1. **Mint a token in a terminal** using the client-credentials request above, and read `access_token` from the response.\n2. **Paste it into the `Bearer Token` field** in the Authentication panel. It applies to every operation on the page.\n3. Open an operation, press **Test Request**, fill in any parameters, then **Send Request**.\n\n**Do not use the Authorize button on the OAuth2 scheme.** It cannot work from a browser: the authorization server sends no cross-origin resource sharing headers on its token endpoint for a client-secret grant — deliberately, because a client secret must never be held by a browser — so the exchange is blocked before any response can be read. The failure surfaces as a bare browser error and looks exactly like a problem with your credential when it is nothing of the kind. The OAuth2 scheme is there to document how a token is obtained; `Bearer Token` is the field to use.\n\nDo not add scopes in the panel either. Your grant is fixed on the credential at approval and travels inside the token; requesting one you do not hold fails the exchange with `403 access_denied` before this API is reached at all.\n\nMost of this surface needs a **delegated** token rather than an organization one — see the next section. Switching between the two is just re-pasting the `Bearer Token` field.\n\n## Acting as a clinician\n\nAn **organization token** sees every clinic, practitioner, patient, and appointment your organization owns. A **delegated token** is scoped to one practitioner and only ever touches that clinician's own data. To request one, add a `practitioner_id` parameter (one of your practitioner ids) to the client-credentials token request; the authorization server confirms the practitioner belongs to your organization and binds it into the token.\n\n**A delegated token is required, not optional, for most of this API.** Everything a clinician personally owns — worksheets, storybooks, playrooms, toolkits, forms, game saves, game sessions, notes, exports, and embed tokens — belongs to one practitioner rather than to an organization, so an organization token has nobody to scope to and every one of those endpoints refuses it with `403`. That is 53 of the 76 operations here. Only the directory, scheduling and session-configuration surfaces (health, clinics, practitioners, patients, appointments, session config) accept an organization token. Plan for minting delegated tokens before you build against the content surfaces; availability depends on your onboarding configuration, so contact PlaySpace to enable it if a `practitioner_id` token request is rejected.\n\nA delegated token narrows what you see and do. Reads return only that clinician's data: the patients list and get return only their roster, and the appointments list and get return only their appointments. The practitioners and clinics directories are unchanged — both remain organization-wide on either token. Writes must be self-attributed: creating an appointment requires `partner_practitioner_id` to equal the token's clinician, and creating a patient requires `created_by_partner_practitioner_id` to equal it, otherwise the request is rejected with a `422`. Updates are limited to that clinician's own appointments and roster patients. Organization-level writes — any practitioner or clinic create, update, or delete, and deleting a patient — require an organization token and return `403` on a delegated token.\n\nAn id outside a delegated token's scope is indistinguishable from one that never existed: both return `404`, so the response cannot be used to probe what lies outside the clinician's data.\n\nYou stay in control of each clinician's access. Set a practitioner's `status` to `disabled` (`PATCH /v1/partner/practitioners/{id}`) and every delegated token for that clinician is rejected with `403` and the `practitioner-disabled` problem type until you set `status` back to `active`. Disabling is reversible and keeps all records — it is the right tool for suspending access, while removal (`DELETE`) is for off-boarding. Organization tokens are unaffected, so you can always see and re-enable a disabled practitioner, and PlaySpace support can also restore access if needed.\n\nPer-clinician tokens multiply token issuance — one per clinician rather than one per organization — so cache one delegated token per clinician and reuse it until shortly before it expires rather than minting a new token per request.\n\n## Tenancy\n\nEvery request is scoped to the partner organization the token was issued to. You never send an organization id in a request — it is derived from the token. Ids that belong to another organization behave exactly like ids that never existed: both return `404`. There is no way to observe, enumerate, or address another organization's data.\n\n## Errors\n\nEvery non-2xx response is an RFC 9457 problem document with content type `application/problem+json`. Each problem body carries `type` (an absolute URI identifying the problem class), `title`, `status`, and `detail`, plus problem-specific extension fields (for example `required_scopes` on a 403 or dependent counts on a 409). Every response — success or error — carries an `X-Request-Id` header; quote it when contacting support.\n\n## Idempotency\n\nEvery `POST`, `PATCH`, and `DELETE` requires an `Idempotency-Key` header: an opaque, caller-chosen string unique to one logical operation. The key makes retries safe:\n\n- A retry with the **same key and the same payload**, within 24 hours of the original call, returns the original call's cached response verbatim — the operation is not performed twice.\n- A retry with the **same key but a different payload** returns `422` with problem type `idempotency-key-mismatch`.\n- A retry sent with the **same key while the original call is still in flight** returns `409` with problem type `idempotency-conflict`; retry once the original settles.\n\nKeys live for 24 hours; after that the same key starts a fresh operation.\n\n## Rate limits\n\nLimits are applied per partner organization across two windows: per minute and per hour. Every response carries `X-RateLimit-Minute-Remaining` / `X-RateLimit-Minute-Reset` and `X-RateLimit-Hour-Remaining` / `X-RateLimit-Hour-Reset`, where each `-Reset` is the time the window refills. The more restrictive window governs. Exceeding either limit returns `429`; the response includes `Retry-After`.\n\n## Pagination\n\nList endpoints are cursor-based. Pass `limit` to set page size — it defaults to 25 and must be between 1 and 100. A larger value is rejected with a 422 rather than clamped, so a page you receive is never quietly smaller than the one you asked for. To fetch the next page, pass the `cursor` value returned in `meta.pagination.next_cursor`; treat the cursor as opaque and never construct one by hand. `next_cursor` is `null` when the list is fully consumed. Any filters you supply are applied before pagination, so they hold consistently across pages.\n\n## Deletion and 404 policy\n\nDeletes are soft: a deleted resource is de-listed rather than destroyed. A deleted resource returns `404` on `GET` and is omitted from list responses, unless the list endpoint supports `include_deleted=true` and you set it. A nonexistent id, another organization's id, and a deleted id all return one identical `404`, and its `detail` never echoes the id you supplied — so ids cannot be probed or enumerated by their response. Repeating a `DELETE` on the same id returns that same `404`; only an `Idempotency-Key` replay of the original delete observes the prior success."
  },
  "tags": [
    {
      "name": "Meta",
      "description": "Service health and connectivity. Use these endpoints to confirm a token authenticates and the API is reachable before exercising any business endpoint."
    },
    {
      "name": "Practitioners",
      "description": "Clinic-bound provider records. Every practitioner belongs to exactly one clinic. A practitioner’s email maps to a login identity, so an email already in use returns `409`. Deleting a practitioner de-lists rather than destroys it."
    },
    {
      "name": "Clinics",
      "description": "Clinics are the partition that practitioners, patients, and appointments belong to. Deleting a clinic de-lists rather than destroys it. A clinic cannot be deleted while it still has active practitioners, patients, or appointments: that returns `409` with problem type `clinic-not-empty` and counts of the blocking dependents. Clear or reassign the dependents first."
    },
    {
      "name": "Patients",
      "description": "Patient records are maintained per organization: a patient created by your organization is yours alone, and the same person may exist independently under another organization. Email is not unique — duplicate emails within or across organizations are legitimate, because families commonly share an inbox. These records carry protected health information; handle and store them accordingly. A delegated token (see \"Acting as a clinician\") narrows this surface to the token clinician's roster."
    },
    {
      "name": "Playrooms",
      "description": "Playrooms are the themed virtual spaces a clinician runs a session in: a name, an age band, a colour theme, and the set of session items available inside. Each playroom belongs to exactly one practitioner, so this surface requires a delegated token (see \"Acting as a clinician\") on every verb; an organization token is refused. Every new practitioner account starts with a set of PlaySpace-created rooms, flagged `is_default`. There is no delete endpoint: unpublish a room with `PATCH` setting `published` to `false`."
    },
    {
      "name": "Toolkits",
      "description": "Toolkits are named bundles of session items, using the same item vocabulary as a playroom but without the room theming. Like playrooms they belong to a single practitioner and require a delegated token on every verb, ship as PlaySpace-created defaults on a new account, and have no delete endpoint — unpublish with `PATCH` setting `published` to `false`."
    },
    {
      "name": "Appointments",
      "description": "Appointments link one practitioner and one patient who belong to the same clinic. There is no delete endpoint: cancel an appointment with `PATCH` setting `status` to `cancelled`. Cancelling removes the appointment from connected calendars and notifies the patient. A delegated token (see \"Acting as a clinician\") narrows this surface to the token clinician's own appointments."
    },
    {
      "name": "Forms",
      "description": "Questionnaires a clinician authors and owns. Each form belongs to a single practitioner, so this surface requires a delegated token (see \"Acting as a clinician\") and always lists that clinician's own library. Only published forms are listed: a draft is still being authored, and answers recorded against one would not be meaningful. Answers are never returned here — a listed form is a blank template. To have a patient fill one, mint an embed token for the form and frame the embedded form surface."
    },
    {
      "name": "Storybooks",
      "description": "Illustrated stories a clinician creates and owns. Every endpoint here requires a delegated token (see \"Acting as a clinician\"): a story belongs to one clinician, so an organization-wide token has nobody to scope to and is refused. Generation is asynchronous and bills per story — the response returns the story in its `generating` state, so poll it rather than retrying the create. Image links expire about an hour after they are issued: re-read the pages for fresh links, or fetch the asset bytes, rather than storing a link."
    },
    {
      "name": "Games",
      "description": "Live sandtray and dollhouse sessions, and the scenes they leave behind. Starting a session returns one ready-to-frame URL per seat — clinician and patient — both bound to the same session key, so the two participants share one scene. Every endpoint here requires a delegated token (see \"Acting as a clinician\"), and the clinician is read from the token rather than from the request. Deleting a saved scene de-lists it rather than destroying it. Scene preview images are served as bytes and most scenes have none, so check `has_thumbnail` before asking for one."
    },
    {
      "name": "Notes",
      "description": "Clinical notes a clinician writes about a session, including the ones PlaySpace drafts automatically from a recorded session. Read-only: a note is the clinician's own record, so this API can return one but can never create, change or remove one. Every endpoint here requires a delegated token (see \"Acting as a clinician\") — a note belongs to one clinician, so an organization-wide token has nobody to scope to and is refused. Each note carries the appointment, patient and practitioner it belongs to as ids on your own records. Bodies are omitted from the list by default and returned on request; a single note always carries its body. The verbatim, speaker-diarised session recording is returned only by GET /v1/partner/notes/{id}, and only when you ask for it with `include=transcript` — never on the list, at any page size."
    },
    {
      "name": "Exports",
      "description": "A complete copy of one clinician's PlaySpace records, delivered as a single ZIP archive. Every endpoint here requires a delegated token (see \"Acting as a clinician\"): an export is one clinician's whole record, so an organization-wide token has nobody to scope to and is refused. Starting one is asynchronous — the response is the export in its `queued` state, so poll it rather than retrying the start. A clinician can have only one export running at a time and that slot is shared with the exports they start themselves, so a start while one is in flight returns `409`. A finished archive stays downloadable for seven days and is then deleted. The download answers a redirect to a short-lived link: follow it and fetch the bytes promptly rather than storing the link."
    },
    {
      "name": "Embed",
      "description": "Short-lived tokens that let your own application frame a PlaySpace surface for one clinician. Mint them server-side, once per session, and never expose your partner credential to a browser. Requires a delegated token (see \"Acting as a clinician\"); the clinician the surface acts as comes from your token, never from the request body. Supply the exact origins that will frame the surface — an origin you omit cannot render it."
    },
    {
      "name": "Clinic community",
      "description": "What the clinicians of one clinic have shared with each other: worksheets, storybooks and forms a practitioner published for colleagues to preview and copy into their own library. Delegated tokens act as one clinician; an organization token may list one clinic's items."
    }
  ],
  "servers": [
    {
      "url": "https://agentic-ps.playspace.health",
      "description": "Production"
    },
    {
      "url": "https://agentic-ps-dev.playspace.health",
      "description": "Development"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "oauth2": []
    }
  ],
  "paths": {
    "/v1/partner/appointments": {
      "get": {
        "operationId": "listPartnerAppointments",
        "summary": "List appointments",
        "description": "Lists your appointments. Narrow the result with `partner_clinic_id`, `partner_practitioner_id`, `partner_patient_id`, and/or `status`, and use `start_at_from` / `start_at_to` for a half-open time window (`[from, to)`) so adjacent windows tile without overlap. Results are ordered by `start_at` ascending with id as the tiebreaker, and the cursor encodes that order. Set `include_deleted=true` to also return cancelled appointments. Requires the `appointments:read` scope.",
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "partner_clinic_id",
            "schema": {
              "format": "uuid",
              "description": "Optional filter. Returns only appointments belonging to this clinic. Omit to return appointments across every clinic you can access.",
              "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            },
            "description": "Optional filter. Returns only appointments belonging to this clinic. Omit to return appointments across every clinic you can access."
          },
          {
            "in": "query",
            "name": "partner_practitioner_id",
            "schema": {
              "format": "uuid",
              "description": "Optional filter. Returns only appointments scheduled with this practitioner. Omit to return appointments across every practitioner you can access. Combinable with the other filters.",
              "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            },
            "description": "Optional filter. Returns only appointments scheduled with this practitioner. Omit to return appointments across every practitioner you can access. Combinable with the other filters."
          },
          {
            "in": "query",
            "name": "partner_patient_id",
            "schema": {
              "format": "uuid",
              "description": "Optional filter. Returns only appointments for this patient. Omit to return appointments across every patient you can access. Combinable with the other filters.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            },
            "description": "Optional filter. Returns only appointments for this patient. Omit to return appointments across every patient you can access. Combinable with the other filters."
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "description": "Optional filter. Returns only appointments in this status, one of `scheduled`, `completed`, `cancelled`, or `no_show`. Omit to return appointments in every status.",
              "example": "scheduled",
              "type": "string",
              "enum": [
                "scheduled",
                "completed",
                "cancelled",
                "no_show"
              ]
            },
            "description": "Optional filter. Returns only appointments in this status, one of `scheduled`, `completed`, `cancelled`, or `no_show`. Omit to return appointments in every status."
          },
          {
            "in": "query",
            "name": "start_at_from",
            "schema": {
              "format": "date-time",
              "description": "Optional inclusive lower bound on `start_at` (ISO-8601 offset-aware datetime). Returns only appointments starting at or after this instant. Pairs with `start_at_to` to form a half-open interval `[start_at_from, start_at_to)`, so adjacent windows tile without overlap.",
              "example": "2026-06-01T00:00:00.000Z",
              "type": "string",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            "description": "Optional inclusive lower bound on `start_at` (ISO-8601 offset-aware datetime). Returns only appointments starting at or after this instant. Pairs with `start_at_to` to form a half-open interval `[start_at_from, start_at_to)`, so adjacent windows tile without overlap."
          },
          {
            "in": "query",
            "name": "start_at_to",
            "schema": {
              "format": "date-time",
              "description": "Optional exclusive upper bound on `start_at` (ISO-8601 offset-aware datetime). Returns only appointments starting strictly before this instant; an appointment whose `start_at` equals this value is not returned. Pairs with `start_at_from` to form a half-open interval `[start_at_from, start_at_to)`, so adjacent windows tile without overlap or gaps.",
              "example": "2026-06-08T00:00:00.000Z",
              "type": "string",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            "description": "Optional exclusive upper bound on `start_at` (ISO-8601 offset-aware datetime). Returns only appointments starting strictly before this instant; an appointment whose `start_at` equals this value is not returned. Pairs with `start_at_from` to form a half-open interval `[start_at_from, start_at_to)`, so adjacent windows tile without overlap or gaps."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When `true`, include cancelled appointments in the response. Defaults to `false`. Accepts `true`/`1`/`on` (case-insensitive) as true and `false`/`0`/`off`/empty or omission as false; any other value is rejected.",
              "example": false,
              "type": "boolean"
            },
            "description": "When `true`, include cancelled appointments in the response. Defaults to `false`. Accepts `true`/`1`/`on` (case-insensitive) as true and `false`/`0`/`off`/empty or omission as false; any other value is rejected."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of appointments, ordered by `start_at` ascending with id as the tiebreaker. `meta.pagination.next_cursor` is `null` once the list is fully consumed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerAppointment",
        "summary": "Create an appointment",
        "description": "Books a real appointment that the clinician sees immediately. The practitioner and patient must both belong to `partner_clinic_id`; if either does not, the request is rejected with a 422 (`same-clinic-mismatch`). Conflict detection looks only at appointments your organization has already booked for this practitioner and returns a 409 on overlap; appointments booked elsewhere are not considered, and the response reveals no detail about the conflicting appointment. Booking a virtual session returns the patient and clinician join links (`patient_video_url` / `clinician_video_url`), and the appointment is added to the practitioner's connected calendar when they have one. `notify_patient` is a non-persisted flag that, when `true`, emails the patient a confirmation; the clinician confirmation and calendar updates always happen regardless. On a delegated token, `partner_practitioner_id` must equal the token's clinician, otherwise a 422. Requires the `appointments:write` scope.",
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The scoping ids (`partner_clinic_id` / `partner_practitioner_id` / `partner_patient_id`), the `start_at` / `end_at` window, the required `session_type`, plus optional `timezone` / `notes` and the non-persisted `notify_patient` flag. Unknown keys are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerAppointmentCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The appointment was created. The `Location` header points at the new appointment, and for a virtual session `patient_video_url` carries the patient join link and `clinician_video_url` the clinician (host) join link.",
            "headers": {
              "Location": {
                "description": "URL of the new appointment.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentWriteResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token does not grant the `appointments:write` scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — either the Idempotency-Key has an in-flight original call still running (problem type `idempotency-conflict`; retriable once it settles) or the organization video policy changed during the write (problem type `video-configuration-changed`; retry with a new Idempotency-Key). Inspect `type` on the problem body to discriminate. An overlapping window for the same practitioner is NOT a conflict and is accepted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation error. The problem `type` slug discriminates the cause: `validation-error` for a malformed body (an unknown key, an invalid scoping id, a bad `start_at` / `end_at` datetime or timezone, or `end_at` not after `start_at`) or a missing `Idempotency-Key`; `same-clinic-mismatch` when the practitioner or patient does not belong to `partner_clinic_id`; and `idempotency-key-mismatch` when the key was reused with a different body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/appointments/{id}": {
      "get": {
        "operationId": "getPartnerAppointment",
        "summary": "Get an appointment",
        "description": "Fetches a single appointment by its id (the id returned on list responses as `data[].id`). For a virtual appointment the response `patient_video_url` carries the patient join link. Requires the `appointments:read` scope.",
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "Identifier of the appointment, the same id returned on list responses as `data[].id`.",
              "example": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b"
            },
            "required": true,
            "description": "Identifier of the appointment, the same id returned on list responses as `data[].id`."
          }
        ],
        "responses": {
          "200": {
            "description": "The appointment at the requested id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerAppointment",
        "summary": "Update an appointment",
        "description": "Partially updates an appointment: reschedule the window, change the status, and/or edit the notes. Accepts any subset of the coupled `start_at` / `end_at` pair, `timezone`, `notes`, and `status`, and at least one substantive field is required (`notify_patient` alone is a 422). A reschedule supplies `start_at` and `end_at` together (both or neither; sending one is a 422); it re-checks the partner-scoped conflict (409 on overlap) and regenerates a virtual appointment's `patient_video_url` / `clinician_video_url`. Setting `status` to `cancelled` is the full cancellation: it removes the appointment from the practitioner's calendars and, when `notify_patient` is `true`, emails the patient; it then drops from the default list (surface with `include_deleted=true`) and a later `GET`/`PATCH` returns a 404. `session_type` and the scoping ids cannot be patched. A delegated token may update only its clinician's own appointments; any other id returns a 404. Requires the `appointments:write` scope.",
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "Identifier of the appointment, the same id returned on list responses as `data[].id`.",
              "example": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b"
            },
            "required": true,
            "description": "Identifier of the appointment, the same id returned on list responses as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Partial-update body. At least one substantive field is required (the coupled `start_at` / `end_at` pair, `timezone`, `notes`, or `status`). Unknown keys are rejected; `session_type` and the scoping ids are not accepted.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerAppointmentUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The appointment was updated. For a virtual reschedule both `patient_video_url` and `clinician_video_url` are regenerated, so they may differ from the prior values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentWriteResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token does not grant the `appointments:write` scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict. The problem `type` slug discriminates: `idempotency-conflict` when the `Idempotency-Key` has an original call still in flight (retriable once it settles); `video-configuration-changed` when the organization video policy moved during the write (retry with a new `Idempotency-Key`). A reschedule onto a window the practitioner is already booked in is NOT a conflict and is accepted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation error. The problem `type` slug discriminates the cause: `validation-error` for a malformed body (supplying exactly one of `start_at` / `end_at`, `end_at` not after `start_at`, no substantive field, an unknown key such as `session_type` or a scoping id, a bad `status` value, datetime, or timezone), an `id` path param that is not a UUID, or a missing `Idempotency-Key`; `idempotency-key-mismatch` when the key was reused with a different body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/appointments/{id}/session-links": {
      "post": {
        "operationId": "mintPartnerAppointmentSessionLinks",
        "summary": "Get an appointment's session join links",
        "description": "Returns the session entry links for an appointment — the patient link (`patient_video_url`) and the clinician (host) link (`clinician_video_url`) — for partners that store appointment ids and fetch links on demand rather than keeping the create response. Each link is a short code that redeems for a session token scoped to the appointment window, so treat them as sensitive. There is one link per person per appointment: the same two come back on every call, follow a reschedule, and stop working once the appointment is cancelled or deleted. Nothing is changed by this call. The response also reports what the links open into (`video_provider`, `waiting_room_enabled`). An in-person appointment answers with the clinician link only and `patient_video_url: null`: its session is the playroom with no video call and no waiting room. A delegated token may fetch links only for its own clinician's appointments; any other id returns a 404. Requires the `appointments:read` scope.",
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "Identifier of the appointment, the same id returned on list responses as `data[].id`.",
              "example": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b"
            },
            "required": true,
            "description": "Identifier of the appointment, the same id returned on list responses as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "200": {
            "description": "The session entry links for the appointment — both for a virtual appointment, the clinician link alone for an in-person one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerAppointmentSessionLinksResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token does not grant the `appointments:read` scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation error. The problem `type` slug discriminates: `validation-error` when the `id` path param is not a UUID or the `Idempotency-Key` is missing; `idempotency-key-mismatch` when the key was reused with a different request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/clinic-community/items": {
      "get": {
        "operationId": "listClinicCommunityItems",
        "summary": "List what the clinic has shared",
        "description": "Lists the worksheets, storybooks and forms clinicians have shared with their clinic, newest first. On a delegated token the clinic is the acting practitioner's own and `partner_clinic_id` is refused with a 422. On an organization token `partner_clinic_id` is required, and an unknown clinic answers an empty page. Titles, descriptions and tags are clinician-authored text; render them, do not log them. Games are never listed.",
        "tags": [
          "Clinic community"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "item_type",
            "schema": {
              "description": "Return only items of one kind. Omit for every kind.",
              "$ref": "#/components/schemas/ClinicCommunityItemType"
            },
            "description": "Return only items of one kind. Omit for every kind."
          },
          {
            "in": "query",
            "name": "partner_clinic_id",
            "schema": {
              "format": "uuid",
              "description": "Organization tokens only: the clinic whose community to list, as returned by the clinics endpoints as `data[].id`. Required on an organization token and refused with a 422 on a delegated token, whose clinic is the acting practitioner's own.",
              "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            },
            "description": "Organization tokens only: the clinic whose community to list, as returned by the clinics endpoints as `data[].id`. Required on an organization token and refused with a 422 on a delegated token, whose clinic is the acting practitioner's own."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of shared items.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClinicCommunityListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "publishClinicCommunityItem",
        "summary": "Share your content with the clinic",
        "description": "Shares one worksheet, storybook or form from the acting practitioner's own library with their clinic. A copy is made for the clinic; the original stays as it was and is not affected by later changes to the shared copy. Content that is not the acting practitioner's, or that is already itself a shared copy, answers 404. Sharing the same content with the same clinic twice answers 409 `community-item-conflict`. Reuse the same Idempotency-Key on a retry to avoid a duplicate. The practice must have the community enabled. REQUIRES a delegated token.",
        "tags": [
          "Clinic community"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Which content to share and how to present it.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClinicCommunityPublishBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The item as the clinic now sees it, with its content.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClinicCommunityDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Community Item Conflict — this content is already shared with the clinic. List the clinic community to find the existing item, or remove it before sharing again.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/clinic-community/items/{id}": {
      "get": {
        "operationId": "getClinicCommunityItem",
        "summary": "Preview a shared item",
        "description": "Returns one shared item with its content: the pages of a worksheet or storybook with short-lived image links, or the questions of a form, in the same shape as that resource's own get-by-id. An item from another clinic, a removed item and an unknown id all answer one identical 404. REQUIRES a delegated token.",
        "tags": [
          "Clinic community"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the clinic-community item, as returned on the list response as `data[].id`.",
              "example": "0d9c8b7a-6f5e-4d4c-8b3a-2f1e0d9c8b7a"
            },
            "required": true,
            "description": "ID of the clinic-community item, as returned on the list response as `data[].id`."
          }
        ],
        "responses": {
          "200": {
            "description": "The shared item and its content.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClinicCommunityDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "unpublishClinicCommunityItem",
        "summary": "Remove an item you shared",
        "description": "Removes a shared item from the clinic community. Only the practitioner who shared it can remove it; a colleague's item, a removed item and an unknown id all answer one identical 404, so the response never confirms an item exists. Copies colleagues already made are unaffected. REQUIRES a delegated token.",
        "tags": [
          "Clinic community"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the clinic-community item, as returned on the list response as `data[].id`.",
              "example": "0d9c8b7a-6f5e-4d4c-8b3a-2f1e0d9c8b7a"
            },
            "required": true,
            "description": "ID of the clinic-community item, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The item was removed. No response body."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/clinic-community/items/{id}/copy": {
      "post": {
        "operationId": "copyClinicCommunityItem",
        "summary": "Copy a shared item into your library",
        "description": "Makes the acting practitioner their own copy of a shared item, so they can open it, edit it and place it on their rooms. The copy is independent of the shared original. A retry with the same Idempotency-Key replays the earlier result; a deliberate second request makes a second copy. An item from another clinic, a removed item and an unknown id all answer one identical 404. The practice must have the community enabled. REQUIRES a delegated token.",
        "tags": [
          "Clinic community"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the clinic-community item, as returned on the list response as `data[].id`.",
              "example": "0d9c8b7a-6f5e-4d4c-8b3a-2f1e0d9c8b7a"
            },
            "required": true,
            "description": "ID of the clinic-community item, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "201": {
            "description": "The new copy's kind and id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClinicCommunityCopyResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/clinics": {
      "get": {
        "operationId": "listPartnerClinics",
        "summary": "List partner clinics",
        "description": "Returns a paginated list of your clinics. Supports cursor pagination and an `include_deleted` toggle for surfacing de-listed clinics. There is no parent-resource filter — clinics sit directly under your organization. Requires the `clinics:read` scope.",
        "tags": [
          "Clinics"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, de-listed clinics are included in the response. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, de-listed clinics are included in the response. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of your clinics. `meta.pagination.next_cursor` is null once the list is fully consumed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerClinicListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerClinic",
        "summary": "Create a partner clinic",
        "description": "Creates a clinic under your organization with the supplied name and timezone. Clinic names are unique per partner and compared case-insensitively; a name already in use by another of your clinics returns 409 `clinic-name-conflict`. This endpoint has no 404 — a clinic has no parent resource that could be missing. The 201 body is the same `PartnerClinicResponse` envelope returned by `GET /v1/partner/clinics/{id}`, and the `Location` header points at the new clinic. This is an organization-level action: it requires an organization token, and a delegated token receives a 403. Requires the `clinics:write` scope.",
        "tags": [
          "Clinics"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Clinic name and IANA timezone for the new clinic. Unknown keys are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerClinicCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The clinic was created. The body matches the `GET/{id}` `PartnerClinicResponse` envelope, and the `Location` header points at the new clinic.",
            "headers": {
              "Location": {
                "description": "Canonical URL of the new clinic — `/v1/partner/clinics/{id}`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerClinicResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Two problem types share this status; inspect the `type` URI before retrying. `clinic-name-conflict` — the supplied name is already in use by another of your clinics; not retriable with the same name. `idempotency-conflict` — the `Idempotency-Key` is still in flight; retriable once it settles or with a fresh key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation — the body did not match `PartnerClinicCreateBody` (unknown key, blank name, or an invalid IANA timezone), the `Idempotency-Key` header was missing, or the key was reused with a different body (`idempotency-key-mismatch`). The `source` extension and problem `type` slug identify which.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/clinics/{id}": {
      "get": {
        "operationId": "getPartnerClinic",
        "summary": "Get a partner clinic by id",
        "description": "Returns a single clinic by id — the value surfaced on LIST responses as `data[].id`. A de-listed clinic is returned only when `include_deleted=true`; otherwise it 404s. Requires the `clinics:read` scope.",
        "tags": [
          "Clinics"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the clinic, as surfaced on LIST responses as `data[].id`.",
              "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "UUID of the clinic, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, the clinic is returned even if it has been de-listed. When false (the default), a de-listed clinic returns 404. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, the clinic is returned even if it has been de-listed. When false (the default), a de-listed clinic returns 404. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          }
        ],
        "responses": {
          "200": {
            "description": "The clinic at the requested id. When `include_deleted=true` and the clinic is de-listed, `data.deleted_at` carries the timestamp at which it was de-listed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerClinicResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerClinic",
        "summary": "Update a partner clinic",
        "description": "Partially updates a clinic. Accepts any subset of `name`, `timezone`; at least one is required. A PATCH whose every supplied value already equals the current value is a 200 no-op that leaves `updated_at` unchanged — setting `name` to the clinic's own current name is therefore a no-op, not a 409. Renaming to a name already in use by another of your clinics returns 409 `clinic-name-conflict` (names are unique per partner, compared case-insensitively). The 200 body is the same `PartnerClinicResponse` envelope returned by `GET /v1/partner/clinics/{id}`. This is an organization-level action: it requires an organization token, and a delegated token receives a 403. Requires the `clinics:write` scope.",
        "tags": [
          "Clinics"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the clinic, as surfaced on LIST responses as `data[].id`.",
              "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "UUID of the clinic, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Partial-update body. At least one of `name`, `timezone` is required. Unknown keys are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerClinicUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The clinic was updated. The body matches the `GET/{id}` `PartnerClinicResponse` envelope. A no-op PATCH (every supplied value already current) also returns 200 with an unchanged `updated_at`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerClinicResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No clinic with this id is visible to you.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Two problem types share this status; inspect the `type` URI before retrying. `clinic-name-conflict` — the supplied name is already in use by another of your clinics; not retriable with the same name. `idempotency-conflict` — the `Idempotency-Key` is still in flight; retriable once it settles or with a fresh key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation — the body did not match `PartnerClinicUpdateBody` (unknown key, blank name, invalid IANA timezone, or no field supplied), the path `id` was not a UUID, the `Idempotency-Key` header was missing, or the key was reused with a different body (`idempotency-key-mismatch`). The `source` extension and problem `type` slug identify which.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deletePartnerClinic",
        "summary": "Soft-delete a partner clinic",
        "description": "De-lists the clinic from your roster; the underlying practice record is unaffected. The delete is rejected with 409 `clinic-not-empty` while any active practitioner, patient, or appointment still references the clinic, and is not retriable until those dependents are cleared. The 409 body carries `practitioner_count`, `patient_count`, and `appointment_count` so a UI can show what blocks the delete without another round-trip; currently only `practitioner_count` is observably non-zero — the other two are reserved and read 0. This is an organization-level action: it requires an organization token, and a delegated token receives a 403. Requires the `clinics:delete` scope.",
        "tags": [
          "Clinics"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the clinic, as surfaced on LIST responses as `data[].id`.",
              "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "UUID of the clinic, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The clinic was de-listed. No response body. Subsequent reads return 404 unless `include_deleted=true`."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No clinic with this id is visible to you. A repeat DELETE on an already de-listed clinic returns this same 404 (only an `Idempotency-Key` replay observes the prior success).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Two problem types share this status; inspect the `type` URI before retrying. `clinic-not-empty` — an active practitioner, patient, or appointment still references this clinic, and the body carries `practitioner_count`, `patient_count`, and `appointment_count` so a UI can show what blocks the delete; not retriable until those dependents are cleared. `idempotency-conflict` — the `Idempotency-Key` is still in flight; retriable once it settles or with a fresh key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ClinicNotEmptyProblem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation — the path `id` was not a UUID, the `Idempotency-Key` header was missing, or the key was reused with a different request (`idempotency-key-mismatch`). The `source` extension and problem `type` slug identify which.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/embed-tokens": {
      "post": {
        "operationId": "createPartnerEmbedToken",
        "summary": "Mint an embed capability token",
        "description": "Exchanges a delegated partner token for a short-lived token that authorises an embedded PlaySpace surface in an iframe. REQUIRES a delegated token: the acting practitioner is read from the pse_subject_partner_practitioner_id claim, never from the request body. Call this server-side on every page load that renders an embed; never expose the partner token to a browser.",
        "tags": [
          "Embed"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerEmbedTokenCreateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A token was minted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerEmbedTokenResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/exports": {
      "post": {
        "operationId": "createPartnerExport",
        "summary": "Start an export of the acting clinician's records",
        "description": "Starts building a single ZIP archive containing everything PlaySpace holds for the clinician the delegated token is acting as — their record, their caseload, and every note, form, worksheet, storybook and upload they own. There is no request body: the export is always \"all of this clinician's data\", and nothing in a request could name anyone else. Building it takes minutes and outlives this call, so the response is the export in its `queued` state rather than a finished archive; poll `GET /v1/partner/exports/{id}` with the returned id and download it once the status is `ready`. A clinician can have only one export running at a time, and that slot is shared with exports they start themselves, so this can return 409 for an export you never asked for. Started exports cannot be cancelled. REQUIRES a delegated token.",
        "tags": [
          "Exports"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "201": {
            "description": "The export was started. The body is the export in its `queued` state — the same shape the status endpoint returns — and the `Location` header points at it.",
            "headers": {
              "Location": {
                "description": "Canonical URL of the new export — `/v1/partner/exports/{id}`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerExportResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — inspect the `type` URI before retrying. `export-in-progress`: this clinician already has an export running, possibly one they started themselves in PlaySpace; there is no payload to change, so wait for it to finish and start again. `idempotency-conflict`: the `Idempotency-Key` has an in-flight original call; retriable once it settles or with a fresh key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/exports/{id}": {
      "get": {
        "operationId": "getPartnerExport",
        "summary": "Get an export by id",
        "description": "Returns the current state of one export belonging to the clinician the delegated token is acting as: how far it has got, how much it has gathered, and — once it is ready — what the finished archive weighs and hashes to. This is the endpoint to poll after starting an export; a few seconds between polls is plenty, since the work takes minutes. An export belonging to another clinician, an id that never existed, and a malformed id are indistinguishable — all three return one identical 404. REQUIRES a delegated token.",
        "tags": [
          "Exports"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "minLength": 1,
              "format": "uuid",
              "description": "ID of the export, as returned by the start operation as `data.id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the export, as returned by the start operation as `data.id`."
          }
        ],
        "responses": {
          "200": {
            "description": "The export at the requested id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerExportResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/exports/{id}/download": {
      "get": {
        "operationId": "downloadPartnerExport",
        "summary": "Download a finished export archive",
        "description": "Hands over the finished archive as a redirect to a short-lived download link rather than as bytes on this response, because an archive can run to gigabytes and the transfer is better made directly against storage — it resumes, and nothing in between can time it out. Follow the redirect and fetch the file promptly; the link stops working after about fifteen minutes and is a credential in its own right, so do not log it, store it, or pass it on. Verify what you receive against the `archive` block on the status endpoint. An export that has not finished returns 409, one whose seven-day window has closed returns 410, and anything else returns 404. REQUIRES a delegated token.",
        "tags": [
          "Exports"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "minLength": 1,
              "format": "uuid",
              "description": "ID of the export, as returned by the start operation as `data.id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the export, as returned by the start operation as `data.id`."
          }
        ],
        "responses": {
          "302": {
            "description": "The archive is ready. The `Location` header carries a short-lived link to the file itself; there is no response body.",
            "headers": {
              "Location": {
                "description": "Short-lived link to the archive. Expires after about fifteen minutes. Treat it as a credential: follow it, then discard it.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (`export-not-ready`) — the export exists but has no archive to hand over: it is either still running or it failed. Poll the export and retry the download once its status is `ready`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "410": {
            "description": "Gone (`export-expired`) — the archive was kept for seven days and has been deleted. Terminal for this export and not retriable; start a new one.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/forms": {
      "get": {
        "operationId": "listPartnerForms",
        "summary": "List the acting practitioner's published forms",
        "description": "Returns the PUBLISHED forms owned by the practitioner the delegated token is acting as, newest first. Drafts are excluded — a draft is a work in progress and filling one would record answers against an instrument the clinician has not finished authoring. REQUIRES a delegated token; an org-wide admin token has no practitioner to scope to and is refused. Pair with an embedded form surface: mint an embed token carrying form:read (plus form:submit and a patient_id to allow saving) and frame any id from this list. Every form here is fillable through the embed; shelf_ready additionally says whether it is eligible to render on a session shelf, so the clinician could reach it from inside a live session.",
        "tags": [
          "Forms"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          }
        ],
        "responses": {
          "200": {
            "description": "The practitioner's published forms.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerFormListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerForm",
        "summary": "Create a form",
        "description": "Authors a new form owned by the practitioner the delegated token is acting as, and returns it. The same two checks the embedded builder applies are applied here — every field needs a unique id and a type the embedded surface can render — so a form created through this endpoint is one the embed can also open; a duplicate id or an unsupported type is a 422 naming the offenders. The new form is NOT shelf-ready: `published` is never set by any create path, so make it attachable with PATCH on this resource before adding it to a playroom or toolkit. `status` defaults to `draft`, which is invisible on every other endpoint here — send `published` unless you mean to finish it in PlaySpace later. REQUIRES a delegated token.",
        "tags": [
          "Forms"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The form to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerFormCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created form.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerFormResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/forms/{id}": {
      "get": {
        "operationId": "getPartnerForm",
        "summary": "Get a form by id",
        "description": "Returns one published form belonging to the practitioner the delegated token is acting as, in the same shape as an entry in the list. Use it to re-check `shelf_ready` after publishing rather than paging the whole library. A draft, a deleted form, another clinician's form and an unknown id all answer 404 — the endpoint does not distinguish them, so it cannot be used to probe which ids exist. REQUIRES a delegated token.",
        "tags": [
          "Forms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "format": "uuid",
              "description": "ID of the form, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the form, as returned on the list response as `data[].id`."
          }
        ],
        "responses": {
          "200": {
            "description": "The form at the requested id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerFormResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerForm",
        "summary": "Make a form shelf-ready",
        "description": "Makes a form eligible to be placed on a playroom or toolkit shelf, and returns it with `shelf_ready` now true. This is the step between authoring a form and attaching it: a form that is not shelf-ready is refused by the attach endpoints with `reason: not_shelf_ready`, and before this endpoint existed the only way through was for the clinician to publish it inside PlaySpace — so an integration that created a form could never put it on a shelf. Send it again on a form that is already shelf-ready and nothing changes. Only a PUBLISHED form can be made shelf-ready; a draft answers 404, because the authoring lifecycle is a separate axis this tier cannot write. REQUIRES a delegated token.",
        "tags": [
          "Forms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "format": "uuid",
              "description": "ID of the form, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the form, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The change to apply.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerFormUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The form after the change, in the same shape as a get-by-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerFormResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deletePartnerForm",
        "summary": "Delete a form",
        "description": "Retires a form and removes it from every playroom and toolkit it sits on, so it stops appearing on any in-session activity shelf. Answers 204. SUBMISSIONS ARE KEPT: each one carries the form structure alongside the answers, so a clinical record stays readable after the template is retired — this deletes the instrument, never the responses. Deleting a form that is already gone is a 404, the same answer as an unknown id. REQUIRES a delegated token.",
        "tags": [
          "Forms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "format": "uuid",
              "description": "ID of the form, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the form, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The form was retired."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/forms/{id}/submissions": {
      "get": {
        "operationId": "listPartnerFormSubmissions",
        "summary": "List a form's submissions",
        "description": "Returns the answer sets recorded against one form, newest CREATED first — the order a submission was started in, not finished in, because one that has been sent and not yet answered has no submission timestamp to sort on. THIS IS THE ONE ENDPOINT ON THIS API THAT RETURNS PATIENT-AUTHORED CLINICAL CONTENT: `data` holds what a patient wrote, so render it, never log it, and keep it out of your own error reporting. Saved drafts (`status: in_progress`) are included alongside completed sets, so check `status` before treating answers as final. `patient_id` is your own patient id, mapped back from PlaySpace's; null when no patient is attached or the person was never registered through this API. Submissions OUTLIVE the form they answer: still returned after `DELETE /v1/partner/forms/{id}` retires the template, and after a clinician reverts it to a draft — both of which make `GET /v1/partner/forms/{id}` answer 404. A completed instrument is a clinical record. REQUIRES a delegated token.",
        "tags": [
          "Forms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "format": "uuid",
              "description": "ID of the form, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the form, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          }
        ],
        "responses": {
          "200": {
            "description": "The form's submissions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerFormSubmissionListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/forms/{id}/submissions/{submissionId}": {
      "get": {
        "operationId": "getPartnerFormSubmission",
        "summary": "Get one submission",
        "description": "Returns one answer set. Same content warning as the list: `data` is patient-authored clinical content. The submission must belong to the form named in the path — a valid submission id from a different form is a 404, not a cross-form read. Like the list, it keeps answering after the form has been deleted or reverted to a draft: the record outlives the template. REQUIRES a delegated token.",
        "tags": [
          "Forms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "format": "uuid",
              "description": "ID of the form."
            },
            "required": true,
            "description": "ID of the form."
          },
          {
            "in": "path",
            "name": "submissionId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "format": "uuid",
              "description": "ID of the submission."
            },
            "required": true,
            "description": "ID of the submission."
          }
        ],
        "responses": {
          "200": {
            "description": "The submission.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerFormSubmissionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/game-saves": {
      "get": {
        "operationId": "listPartnerGameSaves",
        "summary": "List the acting clinician's saved scenes",
        "description": "Returns the sandtray and dollhouse scenes the clinician the delegated token is acting as has saved, newest first. Filter by `game_type`, by `patient_id`, or both. The scene contents are not on this response — read one scene to get them. Deleted scenes are omitted unless `include_deleted=true`. Scene names are clinician free text and can reference the child, so treat the response as clinical content. REQUIRES a delegated token — an org-wide token has no clinician to scope to and is refused.",
        "tags": [
          "Games"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "game_type",
            "schema": {
              "description": "Return only sandtray scenes, or only dollhouse ones. Omit for both.",
              "$ref": "#/components/schemas/PartnerGameType"
            },
            "description": "Return only sandtray scenes, or only dollhouse ones. Omit for both."
          },
          {
            "in": "query",
            "name": "patient_id",
            "schema": {
              "format": "uuid",
              "description": "Return only the scenes filed against one of your patients. A patient id you do not own matches nothing rather than erroring.",
              "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            },
            "description": "Return only the scenes filed against one of your patients. A patient id you do not own matches nothing rather than erroring."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, deleted scenes are included, each carrying `is_active: false`. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, deleted scenes are included, each carrying `is_active: false`. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          }
        ],
        "responses": {
          "200": {
            "description": "The clinician's saved scenes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerGameSaveListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/game-saves/{id}": {
      "get": {
        "operationId": "getPartnerGameSave",
        "summary": "Get one saved scene, with its contents",
        "description": "Returns one saved scene belonging to the clinician the delegated token is acting as, including the full scene payload. That payload is what a child built in a therapy session — render or store it as clinical content, never log it. A deleted scene is a 404 unless `include_deleted=true`. REQUIRES a delegated token.",
        "tags": [
          "Games"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the scene, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the scene, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted scene is returned (carrying `is_active: false`) instead of answering 404, and its thumbnail stays readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted scene is returned (carrying `is_active: false`) instead of answering 404, and its thumbnail stays readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The scene at the requested id, with its contents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerGameSaveDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deletePartnerGameSave",
        "summary": "Delete a saved scene",
        "description": "Removes a scene from the clinician's history. This is a soft delete: the scene drops out of the default list and its contents stop being readable, but nothing is destroyed, so it can still be surfaced with `include_deleted=true`. A repeated delete on the same id returns 404, not an error. REQUIRES a delegated token.",
        "tags": [
          "Games"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the scene, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the scene, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The scene was deleted. No response body."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/game-saves/{id}/thumbnail": {
      "get": {
        "operationId": "downloadPartnerGameSaveThumbnail",
        "summary": "Download a scene's preview image",
        "description": "Returns the preview image for a scene, as image bytes rather than a link. Check `has_thumbnail` on the scene first: most scenes have no preview, and one that does not returns 404 rather than an empty response. The content type is read from the image itself. REQUIRES a delegated token.",
        "tags": [
          "Games"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the scene, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the scene, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted scene is returned (carrying `is_active: false`) instead of answering 404, and its thumbnail stays readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted scene is returned (carrying `is_active: false`) instead of answering 404, and its thumbnail stays readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The preview image. The content type is whatever the image actually is.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/game-sessions": {
      "post": {
        "operationId": "createPartnerGameSession",
        "summary": "Start a sandtray or dollhouse session",
        "description": "Starts a live two-seat scene for one clinician and one patient and returns a ready-to-frame URL for each. Both seats share one session key, so whatever the clinician builds appears in the patient's frame as it happens. Call this server-side per session and hand each URL to its own participant; never expose your partner token to a browser. To refresh tokens on a session already running, call this again with that session's `game_session_id` — a frame refuses a token minted for a different session. Scenes saved during the session are readable afterwards through the game-saves endpoints. REQUIRES a delegated token — the clinician is read from the token, not the body. An unknown patient id, one belonging to another organization, and one outside the acting clinician's roster all answer 404.",
        "tags": [
          "Games"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Which scene to run, for which patient, and which origins may frame it.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerGameSessionCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The session is live and both seats can join. `Location` names the session, which has no read endpoint of its own — the two seats are the resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerGameSessionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/health": {
      "get": {
        "operationId": "getPartnerApiHealth",
        "summary": "Partner API health check",
        "description": "Lightweight authenticated probe. Returns 200 with the partner organization id and a server timestamp whenever the request token is accepted. Always-accessible — no scopes required.",
        "tags": [
          "Meta"
        ],
        "responses": {
          "200": {
            "description": "The partner token authenticated successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/notes": {
      "get": {
        "operationId": "listPartnerNotes",
        "summary": "List the acting clinician's notes",
        "description": "Returns the clinical notes written by the clinician the delegated token is acting as, newest first. Each note carries the appointment, patient and practitioner it belongs to as ids on your own records, so you can file it against the encounter you booked. Bodies are omitted by default — add `include=content` to get every listed note's written body and version number in the same response, in one request rather than one per note. A note taken during an ad-hoc recording, rather than during an appointment you booked, still appears here with null appointment and patient ids. REQUIRES a delegated token: an organization-wide token has no clinician to scope to and is refused. This surface is read-only — notes cannot be created, changed or removed through this API.",
        "tags": [
          "Notes"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "Set to `content` to get every listed note's written body and version number in the same response. Omitted by default: a page of notes returns titles and timing so you can decide what to open, and the bodies are a much larger release of clinical information than the list itself. One request either way — never one per note.",
              "example": "content",
              "type": "string",
              "const": "content"
            },
            "description": "Set to `content` to get every listed note's written body and version number in the same response. Omitted by default: a page of notes returns titles and timing so you can decide what to open, and the bodies are a much larger release of clinical information than the list itself. One request either way — never one per note."
          }
        ],
        "responses": {
          "200": {
            "description": "The clinician's notes, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerNoteListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/notes/{id}": {
      "get": {
        "operationId": "getPartnerNote",
        "summary": "Get a note by id",
        "description": "Returns one clinical note written by the clinician the delegated token is acting as, always including its written body and version number — a note read one at a time is the case where the body is the answer. Add `include=transcript` to get the verbatim, speaker-diarised recording of the session alongside it; that is available here and on no other endpoint, because a transcript is every word both people said rather than what the clinician chose to record, and a long session runs to megabytes. Treat the whole payload as clinical content: render it, do not log it. A note belonging to another clinician is indistinguishable from one that never existed — both return 404. REQUIRES a delegated token.",
        "tags": [
          "Notes"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the note, as returned on the list response as `data[].id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the note, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "Set to `transcript` to get the verbatim, speaker-diarised recording of the session alongside the clinician's written note. Omitted by default, and available on this endpoint only: a transcript is every word both people said rather than what the clinician chose to record, and a long session runs to megabytes. Ask for it one note at a time, when you have a reason to.",
              "example": "transcript",
              "type": "string",
              "const": "transcript"
            },
            "description": "Set to `transcript` to get the verbatim, speaker-diarised recording of the session alongside the clinician's written note. Omitted by default, and available on this endpoint only: a transcript is every word both people said rather than what the clinician chose to record, and a long session runs to megabytes. Ask for it one note at a time, when you have a reason to."
          }
        ],
        "responses": {
          "200": {
            "description": "The note at the requested id, with its written body, and its transcript when `include=transcript` was set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerNoteDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/patients": {
      "get": {
        "operationId": "listPartnerPatients",
        "summary": "List patients",
        "description": "List your patients across your clinics. Use `partner_clinic_id` to narrow to one clinic, `partner_practitioner_id` to narrow to patients assigned to one practitioner, or both together to scope to one practitioner within one clinic. Supports pagination and an `include_deleted` toggle. Requires the `patients:read` scope.",
        "tags": [
          "Patients"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "partner_clinic_id",
            "schema": {
              "format": "uuid",
              "description": "Optional filter. Return only patients belonging to this clinic. When omitted, returns patients across every clinic you can access.",
              "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            },
            "description": "Optional filter. Return only patients belonging to this clinic. When omitted, returns patients across every clinic you can access."
          },
          {
            "in": "query",
            "name": "partner_practitioner_id",
            "schema": {
              "format": "uuid",
              "description": "Optional filter. Return only patients assigned to this practitioner. May be combined with `partner_clinic_id` to scope to one practitioner within one clinic. When omitted, returns patients across every practitioner you can access.",
              "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            },
            "description": "Optional filter. Return only patients assigned to this practitioner. May be combined with `partner_clinic_id` to scope to one practitioner within one clinic. When omitted, returns patients across every practitioner you can access."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, include patients that have been removed from the roster. Defaults to false. Accepts \"true\"/\"1\"/\"on\" as true and \"false\"/\"0\"/\"off\"/\"\" or omission as false; any other value is a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, include patients that have been removed from the roster. Defaults to false. Accepts \"true\"/\"1\"/\"on\" as true and \"false\"/\"0\"/\"off\"/\"\" or omission as false; any other value is a 422."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of patient records.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPatientListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerPatient",
        "summary": "Create a patient",
        "description": "Creates a patient. The new patient is anchored to `partner_clinic_id` and credited to `created_by_partner_practitioner_id`, which also becomes the patient's first linked practitioner. Both ids must reference your own active clinic and practitioner; the practitioner may be in any clinic, not only `partner_clinic_id`. An invalid or unknown value for either id is a 422.  Email is optional and has no uniqueness check (see the Patients tag); a duplicate email succeeds. The 201 response carries the new patient record and a `Location` header pointing at it.  On a delegated token, `created_by_partner_practitioner_id` must equal the token's clinician, otherwise a 422.  Requires the `patients:write` scope.",
        "tags": [
          "Patients"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Identity and contact fields for the new patient, plus the `partner_clinic_id` anchor and the `created_by_partner_practitioner_id` creator. See PartnerPatientCreateBody.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerPatientCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The patient was created. The Location header points at the new resource.",
            "headers": {
              "Location": {
                "description": "Canonical URL of the new patient — /v1/partner/patients/{id}.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPatientCreateResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token does not grant the `patients:write` scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error. The body failed validation (unknown key, blank name, malformed date, or invalid timezone), the Idempotency-Key was missing, or `partner_clinic_id` / `created_by_partner_practitioner_id` did not reference one of your active records. There is no 409 on this endpoint.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/patients/{id}": {
      "get": {
        "operationId": "getPartnerPatient",
        "summary": "Get a patient by id",
        "description": "Returns a single patient by id (the same id surfaced on LIST responses as `data[].id`).  Pass `?include_linked_practitioners=true` to add a `linked_partner_practitioner_ids` field listing the practitioners currently linked to this patient. It defaults to false and is omitted otherwise.  Requires the `patients:read` scope.",
        "tags": [
          "Patients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the patient, as returned on LIST responses as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the patient, as returned on LIST responses as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_linked_practitioners",
            "schema": {
              "description": "When true, add a `linked_partner_practitioner_ids` field to the response listing the practitioners currently linked to this patient. Defaults to false, in which case the field is omitted. Accepts \"true\"/\"1\"/\"on\" as true and \"false\"/\"0\"/\"off\"/\"\" or omission as false; any other value is a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, add a `linked_partner_practitioner_ids` field to the response listing the practitioners currently linked to this patient. Defaults to false, in which case the field is omitted. Accepts \"true\"/\"1\"/\"on\" as true and \"false\"/\"0\"/\"off\"/\"\" or omission as false; any other value is a 422."
          }
        ],
        "responses": {
          "200": {
            "description": "The patient record at the requested id. Carries `linked_partner_practitioner_ids` only when `include_linked_practitioners=true`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPatientResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No patient exists at this id within your scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerPatient",
        "summary": "Update a patient",
        "description": "Partially updates a patient. Accepts any subset of the mutable fields — `first_name`, `last_name`, `preferred_name`, `date_of_birth`, `email`, `phone_number`, `timezone` — and at least one is required. `partner_clinic_id` and `created_by_partner_practitioner_id` are not accepted; both are fixed at creation.  A PATCH whose supplied values all equal the current values returns 200 with the current record. Email is mutable and has no uniqueness check (see the Patients tag): setting any value, including another patient's email, succeeds, though a malformed email is still a 422.  The 200 response carries the updated patient record without the `linked_partner_practitioner_ids` field.  A delegated token may update only patients on its clinician's roster; any other id returns a 404.  Requires the `patients:write` scope.",
        "tags": [
          "Patients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the patient, as returned on LIST responses as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the patient, as returned on LIST responses as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Partial-update body. At least one mutable field is required. See PartnerPatientUpdateBody.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerPatientUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The patient was updated. A no-op PATCH whose supplied values all equal the current values also returns 200 with the current record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPatientUpdateResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token does not grant the `patients:write` scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No patient exists at this id within your scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key has an in-flight original call still running. Retriable once it settles or with a fresh key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error. The body failed validation (no field supplied, a blank name, a malformed date, email, or timezone, or an unknown key), the path `id` was not a UUID, the Idempotency-Key was missing, or the Idempotency-Key was reused with a different body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deletePartnerPatient",
        "summary": "Soft-delete a patient",
        "description": "Removes the patient from your roster. This is a soft delete: the patient drops out of the default LIST (surface it again with `include_deleted=true`) and GET-by-id and a subsequent PATCH or DELETE on the same id return 404. A repeated DELETE on the same id is therefore a 404, not an error.  A patient with future appointments can still be deleted in this version; those appointments keep referencing the patient.  Deleting a patient is an organization-level action: it requires an organization token, and a delegated token receives a 403.  Requires the `patients:delete` scope.",
        "tags": [
          "Patients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the patient, as returned on LIST responses as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the patient, as returned on LIST responses as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The patient was removed from the roster. No response body. Subsequent GET-by-id returns 404, and LIST omits the row unless `include_deleted=true`."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The token does not grant the `patients:delete` scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No patient exists at this id within your scope. A second DELETE on an already-removed id returns the same 404.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key has an in-flight original call still running. Retriable once it settles or with a fresh key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error. The path `id` was not a UUID, the Idempotency-Key was missing, or the Idempotency-Key was reused with a different id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/playrooms": {
      "get": {
        "operationId": "listPartnerPlayrooms",
        "summary": "List the acting practitioner's playrooms",
        "description": "Returns the playrooms owned by the practitioner the delegated token is acting as, newest first, including the starter rooms created with the account (`is_default`). Supports cursor pagination, a `published` filter, and an `include_deleted` toggle. REQUIRES a delegated token — an organization token has no practitioner to scope to and is refused.",
        "tags": [
          "Playrooms"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, removed rooms are included. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, removed rooms are included. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          },
          {
            "in": "query",
            "name": "published",
            "schema": {
              "description": "Filter by publication state: `true` returns only rooms offered at session start, `false` only drafts. Omit to return both.",
              "example": true,
              "type": "boolean"
            },
            "description": "Filter by publication state: `true` returns only rooms offered at session start, `false` only drafts. Omit to return both."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of the practitioner's playrooms. `meta.pagination.next_cursor` is null once the list is fully consumed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPlayroomListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerPlayroom",
        "summary": "Create a playroom for the acting practitioner",
        "description": "Creates a playroom owned by the practitioner the delegated token is acting as. Only `title` is required; `room_type`, `color_palette`, `session_items`, `tags`, and `published` fall back to the same defaults the PlaySpace app applies, and the room thumbnail is derived from `room_type` and `color_palette`. Titles are not unique — two rooms may share one, so there is no conflict response for a duplicate name. The 201 body is the same `PartnerPlayroomResponse` envelope returned by `GET /v1/partner/playrooms/{id}`, and the `Location` header points at the new room. REQUIRES a delegated token — an organization token is refused, because a room must belong to one clinician.",
        "tags": [
          "Playrooms"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The new room. Only `title` is required. Unknown keys are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerPlayroomCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The room was created. The body matches the `GET/{id}` `PartnerPlayroomResponse` envelope, and the `Location` header points at the new room.",
            "headers": {
              "Location": {
                "description": "Canonical URL of the new room — `/v1/partner/playrooms/{id}`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPlayroomResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation — the body did not match `PartnerPlayroomCreateBody` (unknown key, blank title, or an unrecognised session item), the `Idempotency-Key` header was missing, or the key was reused with a different body (`idempotency-key-mismatch`). The `source` extension and problem `type` slug identify which.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/playrooms/{id}": {
      "get": {
        "operationId": "getPartnerPlayroom",
        "summary": "Get a playroom by id",
        "description": "Returns a single playroom by id — the value surfaced on LIST responses as `data[].id`. A removed room is returned only when `include_deleted=true`; otherwise it 404s, as does a room belonging to any other practitioner. REQUIRES a delegated token.",
        "tags": [
          "Playrooms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, the room is returned even if it has been removed. When false (the default), a removed room returns 404. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, the room is returned even if it has been removed. When false (the default), a removed room returns 404. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          }
        ],
        "responses": {
          "200": {
            "description": "The room at the requested id. When `include_deleted=true` and the room has been removed, `data.deleted_at` carries the timestamp.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPlayroomResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerPlayroom",
        "summary": "Update a playroom",
        "description": "Partially updates a playroom. Accepts any subset of the create fields; at least one is required. `session_items` and `tags` replace the current arrays wholesale rather than merging. Changing `room_type` or `color_palette` re-derives the thumbnail. A PATCH whose every supplied value already equals the current value is a 200 no-op that leaves `updated_at` unchanged. A room belonging to another practitioner, or one that has been removed, returns 404. The 200 body is the same `PartnerPlayroomResponse` envelope returned by `GET /v1/partner/playrooms/{id}`. REQUIRES a delegated token.",
        "tags": [
          "Playrooms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Partial-update body. At least one field is required. Unknown keys are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerPlayroomUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The room was updated. The body matches the `GET/{id}` `PartnerPlayroomResponse` envelope. A no-op PATCH also returns 200, with `updated_at` unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPlayroomResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation — the body did not match `PartnerPlayroomUpdateBody` (empty body, unknown key, blank title, or an unrecognised session item), the `Idempotency-Key` header was missing, or the key was reused with a different body (`idempotency-key-mismatch`). The `source` extension and problem `type` slug identify which.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/playrooms/{id}/content": {
      "get": {
        "operationId": "listPartnerPlayroomContent",
        "summary": "List the content on a playroom's shelf",
        "description": "Returns every worksheet and storybook attached to this playroom's activity shelf. Items are ordered by `order_index` where the pairing carries one and by attachment time otherwise, and the whole shelf comes back in one response — there is no pagination. The response identifies the content rather than embedding it: read a worksheet or a storybook through its own endpoint. A playroom belonging to another practitioner, or one that has been removed, returns 404. Requires the `practitioners:read` scope, and REQUIRES a delegated token — a playroom belongs to one clinician, so an organization token has no practitioner to scope to and is refused.",
        "tags": [
          "Playrooms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`."
          }
        ],
        "responses": {
          "200": {
            "description": "The playroom's shelf. An empty `data` array means the playroom has no content on it, which is a legitimate state rather than an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — one identical response for every reason the request cannot be satisfied: the playroom or toolkit is unknown, belongs to another clinician, or has been removed; the worksheet, storybook or form is unknown, belongs to another clinician, or has been removed; or the attachment does not exist. The body never says which, and never echoes an id, so ids cannot be probed by trying them.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/playrooms/{id}/content/{contentType}/{contentId}": {
      "put": {
        "operationId": "attachPartnerPlayroomContent",
        "summary": "Attach a worksheet or storybook to a playroom",
        "description": "Puts one worksheet or storybook on this playroom's activity shelf. The attachment is identified entirely by its URL, so the request takes no body and repeating it is safe: the first call returns 201 with a `Location` header, and every later call for the same pair returns 200 with no `Location` and changes nothing. `contentType` is `worksheet` or `storybook`; anything else is a 422 naming `path` as the source. Content that exists and is yours but is not in a shelf-renderable published state is refused with 422 and `reason: not_shelf_ready` — check `shelf_ready` on the content lists first. An unknown playroom or content id returns 404. Requires the `practitioners:write` scope, and REQUIRES a delegated token.",
        "tags": [
          "Playrooms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "path",
            "name": "contentType",
            "schema": {
              "$ref": "#/components/schemas/PartnerContentType"
            },
            "required": true,
            "description": "Kind of content a shelf entry holds. `worksheet`, `storybook` and `form` are the three members defined today. **This is an open vocabulary on the way out.** PlaySpace adds content types over time — games are the next one — and a new member will begin appearing as `content_type` on these responses without a new API version, a new path, or any other advance signal. **Your client MUST ignore any `content_type` value it does not recognise**: skip the item, or render it generically. A client that throws, discards the whole response, or fails its own schema validation on an unknown member will break the first time a clinician puts a new kind of content on a shelf. On the way in the vocabulary is closed: a `contentType` path segment outside the list above is rejected with `422`."
          },
          {
            "in": "path",
            "name": "contentId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "200": {
            "description": "The content was already on the shelf, so nothing changed. The body is the existing attachment and there is no `Location` header — that is how a caller tells a re-attach from a first attach.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentItemResponse"
                }
              }
            }
          },
          "201": {
            "description": "The content was put on the shelf. The `Location` header points at the new attachment.",
            "headers": {
              "Location": {
                "description": "Canonical URL of the attachment — `/v1/partner/playrooms/{id}/content/{contentType}/{contentId}`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentItemResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — one identical response for every reason the request cannot be satisfied: the playroom or toolkit is unknown, belongs to another clinician, or has been removed; the worksheet, storybook or form is unknown, belongs to another clinician, or has been removed; or the attachment does not exist. The body never says which, and never echoes an id, so ids cannot be probed by trying them.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error, in one of two kinds. Either the request could not be understood — an unrecognised `contentType` path segment or a malformed id (`source` is `path`), or a missing or reused `Idempotency-Key`. Or the content exists and is yours but is not shelf-ready: its publication state is not the one the in-session shelf renders, so attaching it would create a shelf entry no session participant can see. That second kind carries `reason: \"not_shelf_ready\"` with `source: \"path\"`; publish the content and retry. Use `shelf_ready` on the content list responses to know in advance which ids an attach will accept.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentAttachProblem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "getPartnerPlayroomContent",
        "summary": "Get one content attachment on a playroom",
        "description": "Returns the shelf entry for one worksheet or storybook on this playroom — the same object the collection endpoint returns, addressed directly, so an integration can confirm one attachment without re-reading the whole shelf. `contentType` is `worksheet` or `storybook`; anything else is a 422 naming `path` as the source. If the playroom or the content is unknown, belongs to another clinician or has been removed, or the content is simply not on the shelf, the answer is one identical 404 that never says which. Requires the `practitioners:read` scope, and REQUIRES a delegated token.",
        "tags": [
          "Playrooms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "path",
            "name": "contentType",
            "schema": {
              "$ref": "#/components/schemas/PartnerContentType"
            },
            "required": true,
            "description": "Kind of content a shelf entry holds. `worksheet`, `storybook` and `form` are the three members defined today. **This is an open vocabulary on the way out.** PlaySpace adds content types over time — games are the next one — and a new member will begin appearing as `content_type` on these responses without a new API version, a new path, or any other advance signal. **Your client MUST ignore any `content_type` value it does not recognise**: skip the item, or render it generically. A client that throws, discards the whole response, or fails its own schema validation on an unknown member will break the first time a clinician puts a new kind of content on a shelf. On the way in the vocabulary is closed: a `contentType` path segment outside the list above is rejected with `422`."
          },
          {
            "in": "path",
            "name": "contentId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`."
          }
        ],
        "responses": {
          "200": {
            "description": "The attachment at the requested playroom, content type, and content id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentItemResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — one identical response for every reason the request cannot be satisfied: the playroom or toolkit is unknown, belongs to another clinician, or has been removed; the worksheet, storybook or form is unknown, belongs to another clinician, or has been removed; or the attachment does not exist. The body never says which, and never echoes an id, so ids cannot be probed by trying them.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the `contentType` path segment is not one of the recognised content types, or an id in the path is not a UUID. The problem body carries `source: \"path\"`. On the detach verb this status is also returned when the `Idempotency-Key` header is missing or was reused with a different request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "detachPartnerPlayroomContent",
        "summary": "Detach content from a playroom",
        "description": "Takes one worksheet or storybook off this playroom's activity shelf. The content itself is untouched and stays in the clinician's library; only the shelf entry goes. Detaching is idempotent — a pair that is already off the shelf still returns 204, so a retry after a dropped connection is safe. `contentType` is `worksheet` or `storybook`; anything else is a 422 naming `path` as the source. A playroom or a content id that is unknown, belongs to another clinician, or has been removed returns 404. Requires the `practitioners:write` scope, and REQUIRES a delegated token.",
        "tags": [
          "Playrooms"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the playroom, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "path",
            "name": "contentType",
            "schema": {
              "$ref": "#/components/schemas/PartnerContentType"
            },
            "required": true,
            "description": "Kind of content a shelf entry holds. `worksheet`, `storybook` and `form` are the three members defined today. **This is an open vocabulary on the way out.** PlaySpace adds content types over time — games are the next one — and a new member will begin appearing as `content_type` on these responses without a new API version, a new path, or any other advance signal. **Your client MUST ignore any `content_type` value it does not recognise**: skip the item, or render it generically. A client that throws, discards the whole response, or fails its own schema validation on an unknown member will break the first time a clinician puts a new kind of content on a shelf. On the way in the vocabulary is closed: a `contentType` path segment outside the list above is rejected with `422`."
          },
          {
            "in": "path",
            "name": "contentId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The content is no longer on the shelf. No response body. Returned whether the call removed the attachment or there was nothing to remove."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — one identical response for every reason the request cannot be satisfied: the playroom or toolkit is unknown, belongs to another clinician, or has been removed; the worksheet, storybook or form is unknown, belongs to another clinician, or has been removed; or the attachment does not exist. The body never says which, and never echoes an id, so ids cannot be probed by trying them.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the `contentType` path segment is not one of the recognised content types, or an id in the path is not a UUID. The problem body carries `source: \"path\"`. On the detach verb this status is also returned when the `Idempotency-Key` header is missing or was reused with a different request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/practitioners": {
      "get": {
        "operationId": "listPartnerPractitioners",
        "summary": "List partner practitioners",
        "description": "Lists your practitioners, newest first. Filter to one clinic with `partner_clinic_id`, and set `include_deleted=true` to include removed practitioners. Requires the `practitioners:read` scope.",
        "tags": [
          "Practitioners"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "partner_clinic_id",
            "schema": {
              "format": "uuid",
              "description": "Return only practitioners belonging to this clinic. When omitted, returns practitioners across all clinics you can access.",
              "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
            },
            "description": "Return only practitioners belonging to this clinic. When omitted, returns practitioners across all clinics you can access."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When `true`, include removed practitioners in the results. Defaults to `false`.",
              "example": false,
              "type": "boolean"
            },
            "description": "When `true`, include removed practitioners in the results. Defaults to `false`."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of practitioners.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPractitionerListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerPractitioner",
        "summary": "Create a partner practitioner",
        "description": "Creates a practitioner in the clinic named by `partner_clinic_id`. The supplied email becomes the practitioner login, so an email already in use by another practitioner returns 409. A `partner_clinic_id` you cannot access returns 422 naming that field. On success the 201 body matches the get-by-id response and the `Location` header points at the new record. Retries follow the standard idempotency contract. This is an organization-level action: it requires an organization token, and a delegated token receives a 403. Requires the `practitioners:write` scope.",
        "tags": [
          "Practitioners"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Identity fields and the clinic for the new practitioner.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerPractitionerCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The practitioner was created. The body matches the get-by-id response, and the `Location` header points at the new record.",
            "headers": {
              "Location": {
                "description": "URL of the new practitioner.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPractitionerResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "A conflict. Inspect the body's `type` to distinguish `partner-email-conflict` (the email is already in use by another practitioner) from `idempotency-conflict`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation. The `source` extension or problem `type` identifies the cause, including a `partner_clinic_id` that does not reference a clinic you can access.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/practitioners/{id}": {
      "get": {
        "operationId": "getPartnerPractitioner",
        "summary": "Get a partner practitioner by id",
        "description": "Returns a single practitioner by id. A removed practitioner returns 404 unless you set `include_deleted=true`. Requires the `practitioners:read` scope.",
        "tags": [
          "Practitioners"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "Identifier of the practitioner, as returned in the `id` field of list and single-record responses.",
              "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
            },
            "required": true,
            "description": "Identifier of the practitioner, as returned in the `id` field of list and single-record responses."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When `true`, return the practitioner even if it has been removed. When `false` (the default), a removed practitioner returns 404.",
              "example": false,
              "type": "boolean"
            },
            "description": "When `true`, return the practitioner even if it has been removed. When `false` (the default), a removed practitioner returns 404."
          }
        ],
        "responses": {
          "200": {
            "description": "The requested practitioner. For a removed practitioner returned via `include_deleted=true`, `data.deleted_at` carries the removal timestamp.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPractitionerResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerPractitioner",
        "summary": "Update a partner practitioner",
        "description": "Updates a practitioner with any subset of `first_name`, `last_name`, `email`, `role`, and `status`; at least one is required. Setting `status` to `disabled` suspends the practitioner's per-clinician (delegated) access without removing any records; setting it back to `active` restores access — the reversible alternative to removal. A PATCH whose supplied values all already match the current record is a no-op and returns 200 with the current record. Changing the email to one already in use by another practitioner returns 409, while setting it to the practitioner's own current email is a no-op. A practitioner cannot be moved to a different clinic. On success the 200 body matches the get-by-id response. Retries follow the standard idempotency contract. This is an organization-level action: it requires an organization token, and a delegated token receives a 403. Requires the `practitioners:write` scope.",
        "tags": [
          "Practitioners"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "Identifier of the practitioner, as returned in the `id` field of list and single-record responses.",
              "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
            },
            "required": true,
            "description": "Identifier of the practitioner, as returned in the `id` field of list and single-record responses."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Fields to change. At least one is required.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerPractitionerUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated practitioner, or the unchanged practitioner when the PATCH was a no-op. The body matches the get-by-id response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerPractitionerResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No practitioner with this id is available to update.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "A conflict. Inspect the body's `type` to distinguish `partner-email-conflict` (the email is already in use by another practitioner), `practitioner-owner-protected` (the practitioner owns a clinic that has moved to direct PlaySpace logins, so disabling them would lock that clinic's administrator out: reassign the owner role first), and `idempotency-conflict`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation. The `source` extension or problem `type` identifies the cause.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deletePartnerPractitioner",
        "summary": "Soft-delete a partner practitioner",
        "description": "Removes the practitioner from your roster. Afterward, get-by-id returns 404 and list omits the practitioner unless `include_deleted=true`. A practitioner with future appointments can still be removed, and those appointments keep referencing the same clinician. Retries follow the standard idempotency contract. This is an organization-level action: it requires an organization token, and a delegated token receives a 403. Requires the `practitioners:delete` scope.",
        "tags": [
          "Practitioners"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "Identifier of the practitioner, as returned in the `id` field of list and single-record responses.",
              "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
            },
            "required": true,
            "description": "Identifier of the practitioner, as returned in the `id` field of list and single-record responses."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The practitioner was removed. No response body. Later reads return 404 unless `include_deleted=true`."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No practitioner with this id is available to remove. A repeat delete of an already-removed practitioner returns this same response.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "A conflict. Inspect the body's `type`. `practitioner-owner-protected`: the practitioner owns a clinic that has moved to direct PlaySpace logins, so removing them would lock that clinic's administrator out; reassign the owner role first, then retry. `idempotency-conflict`: retriable once the original call settles or with a fresh key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation. The `source` extension or problem `type` identifies the cause.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/session-config": {
      "get": {
        "operationId": "getPartnerSessionConfig",
        "summary": "Read your organization's session configuration",
        "description": "Returns the configuration your PlaySpace sessions and framed workspace run on, including `partner_editable_fields` — the list of fields you may change. An organization PlaySpace has not configured yet gets the defaults it is actually running on rather than an empty response, with an empty `partner_editable_fields` meaning every field is locked. Read this before and after a change: it is the only way to confirm what a change did.",
        "tags": [
          "Session configuration"
        ],
        "responses": {
          "200": {
            "description": "Your organization's configuration as it stands now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerSessionConfigResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerSessionConfig",
        "summary": "Change your organization's session configuration",
        "description": "Changes one or more configuration fields. Only the fields named in `partner_editable_fields` may be changed; any other field is refused with 403 and NOTHING in the request is applied, so a request that mixes an allowed field with a locked one leaves the configuration exactly as it was. The 403 names the fields it refused. `partner_editable_fields` and `is_active` cannot be changed through this API at all and are rejected as unknown fields. Turning a feature off in `enabled_features` removes it from the framed workspace for every clinician in your organization; turning one on cannot grant a feature your organization is not entitled to.",
        "tags": [
          "Session configuration"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerSessionConfigPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The configuration after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerSessionConfigResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/storybooks": {
      "get": {
        "operationId": "listPartnerStorybooks",
        "summary": "List the acting practitioner's storybooks",
        "description": "Returns storybooks owned by the practitioner the delegated token is acting as, newest first. Deleted books are omitted unless `include_deleted=true`, which returns them with a `deleted_at` timestamp. Add `include=pages` to get every book's pages and image links in the same response instead of one request per book. REQUIRES a delegated token — an org-wide admin token has no practitioner to scope to and is refused. Pair with an embedded reader to open a book: the same embed token that lists here can render any of these ids.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, deleted storybooks are included, each carrying the `deleted_at` timestamp. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, deleted storybooks are included, each carrying the `deleted_at` timestamp. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "Set to `pages` to get every listed book's pages, with their text and image links, in the same response. One request instead of one per book.",
              "example": "pages",
              "type": "string",
              "const": "pages"
            },
            "description": "Set to `pages` to get every listed book's pages, with their text and image links, in the same response. One request instead of one per book."
          }
        ],
        "responses": {
          "200": {
            "description": "The practitioner's storybooks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerStorybookListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerStorybook",
        "summary": "Generate a storybook",
        "description": "Starts generating an illustrated storybook for the acting practitioner and returns it straight away with status `generating` and no pages yet. Illustration takes a minute or two — poll the returned id until the status is `ready` (or `failed`). Generation costs money and cannot be cancelled once accepted, so send a fresh Idempotency-Key per book and reuse that key on any retry: a repeated key returns the original book instead of generating a second one. The practice must have storybook generation enabled. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "What the story should be about, and how it should read and look.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerStorybookCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Generation has started. The body is the same shape as a get-by-id, with an empty page list. `Location` points at the book.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerStorybookDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/storybooks/{id}": {
      "get": {
        "operationId": "getPartnerStorybook",
        "summary": "Get a storybook by id",
        "description": "Returns one storybook belonging to the practitioner the delegated token is acting as, with its pages in reading order. Page text is the story written for a child and page images are short-lived signed links, so treat the whole payload as clinical content: render it, do not log it. A book that is still generating returns with an empty `pages` array. A deleted book is a 404 unless `include_deleted=true`. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The storybook at the requested id, with its pages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerStorybookDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerStorybook",
        "summary": "Make a storybook shelf-ready",
        "description": "Makes a storybook eligible to be placed on a playroom or toolkit shelf, and returns the book with `shelf_ready` now true. This is the step between generating a book and attaching it: a book that is not shelf-ready is refused by the attach endpoints with `reason: not_shelf_ready`. Publishing a book that is still generating is allowed — it becomes attachable straight away and fills in as it finishes — so check `status` first if you want a finished book on the shelf. Send it again on a book that is already shelf-ready and nothing changes. PRECONDITION: only a book still in the practitioner's own library — a draft, or one already on a playroom or toolkit shelf — can be made shelf-ready. A book they shared to the community or to their clinic, and a client's own copy, answer 404 here even though the list and get-by-id both return them. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The change to apply.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerStorybookUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The storybook after the change, in the same shape as a get-by-id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerStorybookDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no book at this id can be made shelf-ready. Either nothing exists at this id for the practitioner you are acting as, or the book exists but is not in their library any more: one shared out to the community or to a clinic, and a client's own copy, are both refused here. A get-by-id on the same id can still return 200.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deletePartnerStorybook",
        "summary": "Delete a storybook",
        "description": "Removes a storybook from the acting practitioner's library. This is a soft delete: the book drops out of the list, any copy already assigned to a client is detached rather than removed, and its stored images are cleared. A repeated delete on the same id returns 404, not an error. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The storybook was deleted. No response body."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/storybooks/{id}/pages": {
      "get": {
        "operationId": "listPartnerStorybookPages",
        "summary": "List a storybook's pages",
        "description": "Returns every page of the book in reading order, with its text and a link to its illustration. Not paginated — a book is small. Useful on its own because the image links expire after an hour: re-read this to refresh them without pulling the whole book. Page text is the story written for a child, so treat the response as clinical content. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The pages of the storybook, in reading order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerStorybookPagesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/storybooks/{id}/pages/{pageId}": {
      "get": {
        "operationId": "getPartnerStorybookPage",
        "summary": "Get one page of a storybook",
        "description": "Returns a single page — its position in the book, its text, and a link to its illustration. A page id that belongs to a different book returns 404, the same as one that does not exist. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook the page belongs to.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook the page belongs to."
          },
          {
            "in": "path",
            "name": "pageId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the page, as returned on the pages response as `data[].id`.",
              "example": "9f8e7d6c-5b4a-4392-8172-6f5e4d3c2b1a"
            },
            "required": true,
            "description": "ID of the page, as returned on the pages response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The requested page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerStorybookPageResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/storybooks/{id}/pages/{pageId}/image": {
      "get": {
        "operationId": "downloadPartnerStorybookPageImage",
        "summary": "Download a page's illustration",
        "description": "Returns the illustration itself, as image bytes rather than a link. Use this when you need to store or re-serve the artwork: the `image_url` on the read endpoints is a temporary link that stops working after an hour, while this endpoint keeps working for as long as the book exists. A page with no illustration yet returns 404. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook the page belongs to.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook the page belongs to."
          },
          {
            "in": "path",
            "name": "pageId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the page, as returned on the pages response as `data[].id`.",
              "example": "9f8e7d6c-5b4a-4392-8172-6f5e4d3c2b1a"
            },
            "required": true,
            "description": "ID of the page, as returned on the pages response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The illustration. The content type is whatever the image was stored as.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/storybooks/{id}/cover": {
      "get": {
        "operationId": "downloadPartnerStorybookCover",
        "summary": "Download a storybook's cover image",
        "description": "Returns the cover image itself, as image bytes rather than a link — the counterpart to the page image endpoint, and the durable alternative to the temporary `thumbnail_url` on the read endpoints. A book with no cover rendered yet returns 404. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The cover image. The content type is whatever the image was stored as.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/storybooks/{id}/download": {
      "get": {
        "operationId": "downloadPartnerStorybook",
        "summary": "Download a storybook as a PDF",
        "description": "Returns the storybook as a PDF — a cover page followed by one page per story page, the same file the clinician downloads in PlaySpace. The response body is the PDF itself, not a JSON envelope, and the filename is carried on `Content-Disposition`. A book with no pages yet (still generating, or generation failed) returns 422 rather than an empty file. Rendering fetches every illustration, so this call is slower than the other reads. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted storybook is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The storybook as a PDF document.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/storybooks/{id}/share": {
      "post": {
        "operationId": "sharePartnerStorybook",
        "summary": "Email a storybook to a patient",
        "description": "Emails the patient a secure link to read the storybook, valid for the requested window. The address is taken from the patient's own record — there is no recipient field, so a book can only ever reach the patient you name, and that patient must be on the acting practitioner's current roster; a patient with no email on file is a 422. The book must be ready: one still generating, or whose generation failed, is a 422 rather than a dead link. Re-sending to the same patient extends the existing link instead of issuing a second one. Send a fresh Idempotency-Key per send: a repeated key returns the original result WITHOUT sending again. REQUIRES a delegated token.",
        "tags": [
          "Storybooks"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the storybook, as returned on the list response as `data[].id`.",
              "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
            },
            "required": true,
            "description": "ID of the storybook, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Which patient to send to, optionally how long the link should last, and an optional note for the email.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerContentShareBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The email was accepted for delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerStorybookShareResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/toolkits": {
      "get": {
        "operationId": "listPartnerToolkits",
        "summary": "List the acting practitioner's toolkits",
        "description": "Returns the toolkits owned by the practitioner the delegated token is acting as, newest first, including the starter toolkits created with the account (`is_default`). Supports cursor pagination, a `published` filter, and an `include_deleted` toggle. REQUIRES a delegated token — an organization token has no practitioner to scope to and is refused.",
        "tags": [
          "Toolkits"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, removed toolkits are included. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, removed toolkits are included. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          },
          {
            "in": "query",
            "name": "published",
            "schema": {
              "description": "Filter by publication state: `true` returns only toolkits offered at session start, `false` only drafts. Omit to return both.",
              "example": true,
              "type": "boolean"
            },
            "description": "Filter by publication state: `true` returns only toolkits offered at session start, `false` only drafts. Omit to return both."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of the practitioner's toolkits. `meta.pagination.next_cursor` is null once the list is fully consumed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerToolkitListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerToolkit",
        "summary": "Create a toolkit for the acting practitioner",
        "description": "Creates a toolkit owned by the practitioner the delegated token is acting as. Only `title` is required; `session_items`, `tags`, and `published` fall back to the same defaults the PlaySpace app applies. Titles are not unique — two toolkits may share one, so there is no conflict response for a duplicate name. The 201 body is the same `PartnerToolkitResponse` envelope returned by `GET /v1/partner/toolkits/{id}`, and the `Location` header points at the new toolkit. REQUIRES a delegated token — an organization token is refused, because a toolkit must belong to one clinician.",
        "tags": [
          "Toolkits"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The new toolkit. Only `title` is required. Unknown keys are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerToolkitCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The toolkit was created. The body matches the `GET/{id}` `PartnerToolkitResponse` envelope, and the `Location` header points at the new toolkit.",
            "headers": {
              "Location": {
                "description": "Canonical URL of the new toolkit — `/v1/partner/toolkits/{id}`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerToolkitResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation — the body did not match `PartnerToolkitCreateBody` (unknown key, blank title, or an unrecognised session item), the `Idempotency-Key` header was missing, or the key was reused with a different body (`idempotency-key-mismatch`). The `source` extension and problem `type` slug identify which.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/toolkits/{id}": {
      "get": {
        "operationId": "getPartnerToolkit",
        "summary": "Get a toolkit by id",
        "description": "Returns a single toolkit by id — the value surfaced on LIST responses as `data[].id`. A removed toolkit is returned only when `include_deleted=true`; otherwise it 404s, as does a toolkit belonging to any other practitioner. REQUIRES a delegated token.",
        "tags": [
          "Toolkits"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, the toolkit is returned even if it has been removed. When false (the default), a removed toolkit returns 404. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, the toolkit is returned even if it has been removed. When false (the default), a removed toolkit returns 404. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          }
        ],
        "responses": {
          "200": {
            "description": "The toolkit at the requested id. When `include_deleted=true` and the toolkit has been removed, `data.deleted_at` carries the timestamp.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerToolkitResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePartnerToolkit",
        "summary": "Update a toolkit",
        "description": "Partially updates a toolkit. Accepts any subset of the create fields; at least one is required. `session_items` and `tags` replace the current arrays wholesale rather than merging. A PATCH whose every supplied value already equals the current value is a 200 no-op that leaves `updated_at` unchanged. A toolkit belonging to another practitioner, or one that has been removed, returns 404. The 200 body is the same `PartnerToolkitResponse` envelope returned by `GET /v1/partner/toolkits/{id}`. REQUIRES a delegated token.",
        "tags": [
          "Toolkits"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Partial-update body. At least one field is required. Unknown keys are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerToolkitUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The toolkit was updated. The body matches the `GET/{id}` `PartnerToolkitResponse` envelope. A no-op PATCH also returns 200, with `updated_at` unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerToolkitResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The request failed validation — the body did not match `PartnerToolkitUpdateBody` (empty body, unknown key, blank title, or an unrecognised session item), the `Idempotency-Key` header was missing, or the key was reused with a different body (`idempotency-key-mismatch`). The `source` extension and problem `type` slug identify which.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/toolkits/{id}/content": {
      "get": {
        "operationId": "listPartnerToolkitContent",
        "summary": "List the content in a toolkit",
        "description": "Returns every worksheet and storybook attached to this toolkit. Items are ordered by `order_index` where the pairing carries one and by attachment time otherwise, and the whole toolkit comes back in one response — there is no pagination. The response identifies the content rather than embedding it: read a worksheet or a storybook through its own endpoint. A toolkit belonging to another practitioner, or one that has been removed, returns 404. Requires the `practitioners:read` scope, and REQUIRES a delegated token — a toolkit belongs to one clinician, so an organization token has no practitioner to scope to and is refused.",
        "tags": [
          "Toolkits"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`."
          }
        ],
        "responses": {
          "200": {
            "description": "The toolkit's content. An empty `data` array means the toolkit holds nothing yet, which is a legitimate state rather than an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — one identical response for every reason the request cannot be satisfied: the playroom or toolkit is unknown, belongs to another clinician, or has been removed; the worksheet, storybook or form is unknown, belongs to another clinician, or has been removed; or the attachment does not exist. The body never says which, and never echoes an id, so ids cannot be probed by trying them.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/toolkits/{id}/content/{contentType}/{contentId}": {
      "put": {
        "operationId": "attachPartnerToolkitContent",
        "summary": "Attach a worksheet or storybook to a toolkit",
        "description": "Puts one worksheet or storybook into this toolkit. The attachment is identified entirely by its URL, so the request takes no body and repeating it is safe: the first call returns 201 with a `Location` header, and every later call for the same pair returns 200 with no `Location` and changes nothing. `contentType` is `worksheet` or `storybook`; anything else is a 422 naming `path` as the source. Content that exists and is yours but is not in a shelf-renderable published state is refused with 422 and `reason: not_shelf_ready` — check `shelf_ready` on the content lists first. An unknown toolkit or content id returns 404. Requires the `practitioners:write` scope, and REQUIRES a delegated token.",
        "tags": [
          "Toolkits"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "path",
            "name": "contentType",
            "schema": {
              "$ref": "#/components/schemas/PartnerContentType"
            },
            "required": true,
            "description": "Kind of content a shelf entry holds. `worksheet`, `storybook` and `form` are the three members defined today. **This is an open vocabulary on the way out.** PlaySpace adds content types over time — games are the next one — and a new member will begin appearing as `content_type` on these responses without a new API version, a new path, or any other advance signal. **Your client MUST ignore any `content_type` value it does not recognise**: skip the item, or render it generically. A client that throws, discards the whole response, or fails its own schema validation on an unknown member will break the first time a clinician puts a new kind of content on a shelf. On the way in the vocabulary is closed: a `contentType` path segment outside the list above is rejected with `422`."
          },
          {
            "in": "path",
            "name": "contentId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "200": {
            "description": "The content was already in the toolkit, so nothing changed. The body is the existing attachment and there is no `Location` header — that is how a caller tells a re-attach from a first attach.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentItemResponse"
                }
              }
            }
          },
          "201": {
            "description": "The content was added to the toolkit. The `Location` header points at the new attachment.",
            "headers": {
              "Location": {
                "description": "Canonical URL of the attachment — `/v1/partner/toolkits/{id}/content/{contentType}/{contentId}`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentItemResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — one identical response for every reason the request cannot be satisfied: the playroom or toolkit is unknown, belongs to another clinician, or has been removed; the worksheet, storybook or form is unknown, belongs to another clinician, or has been removed; or the attachment does not exist. The body never says which, and never echoes an id, so ids cannot be probed by trying them.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error, in one of two kinds. Either the request could not be understood — an unrecognised `contentType` path segment or a malformed id (`source` is `path`), or a missing or reused `Idempotency-Key`. Or the content exists and is yours but is not shelf-ready: its publication state is not the one the in-session shelf renders, so attaching it would create a shelf entry no session participant can see. That second kind carries `reason: \"not_shelf_ready\"` with `source: \"path\"`; publish the content and retry. Use `shelf_ready` on the content list responses to know in advance which ids an attach will accept.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentAttachProblem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "getPartnerToolkitContent",
        "summary": "Get one content attachment in a toolkit",
        "description": "Returns the entry for one worksheet or storybook in this toolkit — the same object the collection endpoint returns, addressed directly, so an integration can confirm one attachment without re-reading the whole toolkit. `contentType` is `worksheet` or `storybook`; anything else is a 422 naming `path` as the source. If the toolkit or the content is unknown, belongs to another clinician or has been removed, or the content is simply not in the toolkit, the answer is one identical 404 that never says which. Requires the `practitioners:read` scope, and REQUIRES a delegated token.",
        "tags": [
          "Toolkits"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "path",
            "name": "contentType",
            "schema": {
              "$ref": "#/components/schemas/PartnerContentType"
            },
            "required": true,
            "description": "Kind of content a shelf entry holds. `worksheet`, `storybook` and `form` are the three members defined today. **This is an open vocabulary on the way out.** PlaySpace adds content types over time — games are the next one — and a new member will begin appearing as `content_type` on these responses without a new API version, a new path, or any other advance signal. **Your client MUST ignore any `content_type` value it does not recognise**: skip the item, or render it generically. A client that throws, discards the whole response, or fails its own schema validation on an unknown member will break the first time a clinician puts a new kind of content on a shelf. On the way in the vocabulary is closed: a `contentType` path segment outside the list above is rejected with `422`."
          },
          {
            "in": "path",
            "name": "contentId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`."
          }
        ],
        "responses": {
          "200": {
            "description": "The attachment at the requested toolkit, content type, and content id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerContentItemResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — one identical response for every reason the request cannot be satisfied: the playroom or toolkit is unknown, belongs to another clinician, or has been removed; the worksheet, storybook or form is unknown, belongs to another clinician, or has been removed; or the attachment does not exist. The body never says which, and never echoes an id, so ids cannot be probed by trying them.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the `contentType` path segment is not one of the recognised content types, or an id in the path is not a UUID. The problem body carries `source: \"path\"`. On the detach verb this status is also returned when the `Idempotency-Key` header is missing or was reused with a different request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "detachPartnerToolkitContent",
        "summary": "Detach content from a toolkit",
        "description": "Takes one worksheet or storybook out of this toolkit. The content itself is untouched and stays in the clinician's library; only the attachment goes. Detaching is idempotent — a pair that is already out of the toolkit still returns 204, so a retry after a dropped connection is safe. `contentType` is `worksheet` or `storybook`; anything else is a 422 naming `path` as the source. A toolkit or a content id that is unknown, belongs to another clinician, or has been removed returns 404. Requires the `practitioners:write` scope, and REQUIRES a delegated token.",
        "tags": [
          "Toolkits"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`.",
              "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
            },
            "required": true,
            "description": "UUID of the toolkit, as surfaced on LIST responses as `data[].id`."
          },
          {
            "in": "path",
            "name": "contentType",
            "schema": {
              "$ref": "#/components/schemas/PartnerContentType"
            },
            "required": true,
            "description": "Kind of content a shelf entry holds. `worksheet`, `storybook` and `form` are the three members defined today. **This is an open vocabulary on the way out.** PlaySpace adds content types over time — games are the next one — and a new member will begin appearing as `content_type` on these responses without a new API version, a new path, or any other advance signal. **Your client MUST ignore any `content_type` value it does not recognise**: skip the item, or render it generically. A client that throws, discards the whole response, or fails its own schema validation on an unknown member will break the first time a clinician puts a new kind of content on a shelf. On the way in the vocabulary is closed: a `contentType` path segment outside the list above is rejected with `422`."
          },
          {
            "in": "path",
            "name": "contentId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`.",
              "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
            },
            "required": true,
            "description": "UUID of the worksheet or storybook, as returned by `GET /v1/partner/worksheets` or `GET /v1/partner/storybooks`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The content is no longer in the toolkit. No response body. Returned whether the call removed the attachment or there was nothing to remove."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — one identical response for every reason the request cannot be satisfied: the playroom or toolkit is unknown, belongs to another clinician, or has been removed; the worksheet, storybook or form is unknown, belongs to another clinician, or has been removed; or the attachment does not exist. The body never says which, and never echoes an id, so ids cannot be probed by trying them.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the `contentType` path segment is not one of the recognised content types, or an id in the path is not a UUID. The problem body carries `source: \"path\"`. On the detach verb this status is also returned when the `Idempotency-Key` header is missing or was reused with a different request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/worksheets": {
      "get": {
        "operationId": "listPartnerWorksheets",
        "summary": "List the acting practitioner's worksheets",
        "description": "Returns the worksheet library of the practitioner the delegated token is acting as, newest first. Deleted worksheets are omitted unless `include_deleted=true`, which returns them with a `deleted_at` timestamp. Add `include=pages` to get every worksheet's pages and image links in the same response instead of one request per worksheet. REQUIRES a delegated token — an org-wide admin token has no practitioner to scope to and is refused. Pair with an embedded viewer to open a worksheet: the same embed token that lists here can render any of these ids. There is no endpoint for creating a worksheet — a worksheet is made by converting a PDF inside an embedded upload surface.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request.",
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous page as `meta.pagination.next_cursor`. Omit on the first request."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 25,
              "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for.",
              "example": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Maximum number of items per page. Defaults to 25. Must be between 1 and 100 — a larger value is rejected with a 422 rather than clamped, so a page is never silently smaller than you asked for."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, deleted worksheets are included, each carrying the `deleted_at` timestamp. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, deleted worksheets are included, each carrying the `deleted_at` timestamp. Defaults to false. Accepts `true`/`1`/`on` (case-insensitive) as true, and `false`/`0`/`off`/`\"\"` or omission as false; any other value is rejected with a 422."
          },
          {
            "in": "query",
            "name": "include",
            "schema": {
              "description": "Set to `pages` to get every listed worksheet's pages, with their text and image links, in the same response. One request instead of one per worksheet.",
              "example": "pages",
              "type": "string",
              "const": "pages"
            },
            "description": "Set to `pages` to get every listed worksheet's pages, with their text and image links, in the same response. One request instead of one per worksheet."
          }
        ],
        "responses": {
          "200": {
            "description": "The practitioner's worksheets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerWorksheetListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPartnerWorksheet",
        "summary": "Create a worksheet",
        "description": "Creates a worksheet in the acting practitioner's library from page images you supply, and returns it in the same shape as a get-by-id. Send the pages base64-encoded in reading order; PNG, JPEG, GIF and WebP are accepted and the format is detected from the bytes. The whole page set travels in one request, so keep the encoded body under roughly 4MB. The new worksheet is immediately eligible to be placed on a playroom or toolkit shelf — its `shelf_ready` is true — and can be attached straight away. `description`, `category` and `tags` are optional and are returned on every subsequent read. Reuse the same Idempotency-Key on a retry to avoid creating a duplicate. The practice must have the creative suite enabled. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The worksheet to create, with its page images.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerWorksheetCreateBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The worksheet was created. The body is the same shape as a get-by-id, with its pages. `Location` points at the worksheet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerWorksheetDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/worksheets/{id}": {
      "get": {
        "operationId": "getPartnerWorksheet",
        "summary": "Get a worksheet by id",
        "description": "Returns one worksheet belonging to the practitioner the delegated token is acting as, with its pages in reading order. Page text and page images are clinician-authored content and the image links are short-lived, so treat the payload as clinical content: render it, do not log it. A worksheet with no pages returns an empty `pages` array. A deleted worksheet is a 404 unless `include_deleted=true`. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the worksheet, as returned on the list response as `data[].id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the worksheet, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The worksheet at the requested id, with its pages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerWorksheetDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deletePartnerWorksheet",
        "summary": "Delete a worksheet",
        "description": "Removes a worksheet from the acting practitioner's library. This is a soft delete: the worksheet drops out of the list, it is removed from any playroom or toolkit that included it, and its stored images are cleared. A repeated delete on the same id returns 404, not an error. A PlaySpace-supplied default worksheet cannot be deleted and returns 422. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the worksheet, as returned on the list response as `data[].id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the worksheet, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "responses": {
          "204": {
            "description": "The worksheet was deleted. No response body."
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/worksheets/{id}/pages": {
      "get": {
        "operationId": "listPartnerWorksheetPages",
        "summary": "List a worksheet's pages",
        "description": "Returns every page of the worksheet in reading order, with its text and a link to its artwork. Not paginated — a worksheet is small. Useful on its own because the image links expire after an hour: re-read this to refresh them without pulling the whole worksheet. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the worksheet, as returned on the list response as `data[].id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the worksheet, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The pages of the worksheet, in reading order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerWorksheetPagesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/worksheets/{id}/pages/{pageId}": {
      "get": {
        "operationId": "getPartnerWorksheetPage",
        "summary": "Get one page of a worksheet",
        "description": "Returns a single page — its position in the worksheet, its text, and a link to its artwork. A page id that belongs to a different worksheet returns 404, the same as one that does not exist. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the worksheet the page belongs to.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the worksheet the page belongs to."
          },
          {
            "in": "path",
            "name": "pageId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the page, as returned on the pages response as `data[].id`.",
              "example": "0d9c8b7a-6f5e-4d4c-8b3a-2f1e0d9c8b7a"
            },
            "required": true,
            "description": "ID of the page, as returned on the pages response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The requested page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerWorksheetPageResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/worksheets/{id}/pages/{pageId}/image": {
      "get": {
        "operationId": "downloadPartnerWorksheetPageImage",
        "summary": "Download a page's image",
        "description": "Returns the page image itself, as image bytes rather than a link. Use this when you need to store or re-serve the artwork: the `image_url` on the read endpoints is a temporary link that stops working after an hour, while this endpoint keeps working for as long as the worksheet exists. This is the stored page background; anything drawn on top of it is baked in by the PDF download, not by this endpoint. A page with no image returns 404. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the worksheet the page belongs to.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the worksheet the page belongs to."
          },
          {
            "in": "path",
            "name": "pageId",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the page, as returned on the pages response as `data[].id`.",
              "example": "0d9c8b7a-6f5e-4d4c-8b3a-2f1e0d9c8b7a"
            },
            "required": true,
            "description": "ID of the page, as returned on the pages response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The page image. The content type is detected from the image itself.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/worksheets/{id}/cover": {
      "get": {
        "operationId": "downloadPartnerWorksheetCover",
        "summary": "Download a worksheet's cover image",
        "description": "Returns the cover image itself, as image bytes rather than a link — the counterpart to the page image endpoint, and the durable alternative to the temporary `thumbnail_url` on the read endpoints. A worksheet with no cover returns 404. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the worksheet, as returned on the list response as `data[].id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the worksheet, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The cover image. The content type is detected from the image itself.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/worksheets/{id}/download": {
      "get": {
        "operationId": "downloadPartnerWorksheet",
        "summary": "Download a worksheet as a PDF",
        "description": "Returns the worksheet as a PDF — one page per worksheet page, the same file the clinician downloads in PlaySpace, with anything drawn on a page composited onto its background. The response body is the PDF itself, not a JSON envelope, and the filename is carried on `Content-Disposition`. A worksheet with no pages returns 422 rather than an empty file. Rendering fetches and composites every page image, so this call is slower than the other reads. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the worksheet, as returned on the list response as `data[].id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the worksheet, as returned on the list response as `data[].id`."
          },
          {
            "in": "query",
            "name": "include_deleted",
            "schema": {
              "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false.",
              "example": false,
              "type": "boolean"
            },
            "description": "When true, a deleted worksheet is returned (with its `deleted_at`) instead of answering 404, and its pages, images, cover and PDF stay readable. Defaults to false."
          }
        ],
        "responses": {
          "200": {
            "description": "The worksheet as a PDF document.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/worksheets/{id}/share": {
      "post": {
        "operationId": "sharePartnerWorksheet",
        "summary": "Email a worksheet to a patient",
        "description": "Emails the patient a secure link to view the worksheet, valid for the requested window. The address is taken from the patient's own record — there is no recipient field, so a worksheet can only ever reach the patient you name, and that patient must be on the acting practitioner's current roster; a patient with no email on file is a 422. A worksheet with no pages is a 422 rather than a dead link, and only library worksheets can be sent — a copy a client has already worked on is not reachable here. Re-sending to the same patient extends the existing link instead of issuing a second one. Send a fresh Idempotency-Key per send: a repeated key returns the original result WITHOUT sending again. REQUIRES a delegated token.",
        "tags": [
          "Worksheets"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "format": "uuid",
              "description": "ID of the worksheet, as returned on the list response as `data[].id`.",
              "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
            },
            "required": true,
            "description": "ID of the worksheet, as returned on the list response as `data[].id`."
          },
          {
            "in": "header",
            "name": "idempotency-key",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract.",
              "example": "4f1c1aa7-9e3c-4f9b-9d4f-8e2c8b6a1c3d"
            },
            "required": true,
            "description": "Required on every POST, PATCH, and DELETE. An opaque, caller-chosen key that must be unique per logical operation. See the Idempotency section of the API introduction for the full retry contract."
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Which patient to send to, optionally how long the link should last, and an optional note for the email.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerContentShareBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The email was accepted for delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerWorksheetShareResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — the request is missing a valid bearer token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — the token is valid but does not authorize this action.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found — no resource exists at this id.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency Conflict — the Idempotency-Key was reused while the original request is still in flight.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error — the request body, query, or path parameters did not match the schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate Limited — the organization has exceeded its rate-limit budget for this window. Retry after the window resets.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Error — an unexpected condition was encountered. Quote the X-Request-Id header when contacting support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PartnerAppointmentCreateBody": {
        "type": "object",
        "properties": {
          "video": {
            "$ref": "#/components/schemas/PartnerAppointmentVideoInput"
          },
          "partner_clinic_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "Identifier of the clinic this appointment is booked under. The practitioner and patient must both belong to this clinic. An unknown or inaccessible id is rejected with a 422.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "partner_practitioner_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "Identifier of the practitioner the appointment is scheduled with. Must belong to `partner_clinic_id`; an unknown or inaccessible id is rejected with a 422.",
            "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
          },
          "partner_patient_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "Identifier of the patient the appointment is for. Must belong to `partner_clinic_id`; an unknown or inaccessible id is rejected with a 422.",
            "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
          },
          "start_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "description": "Appointment start as an ISO-8601 offset-aware datetime. Must be strictly before `end_at`. There is no future-only constraint, so historical appointments may be backfilled.",
            "example": "2026-06-02T15:00:00.000Z"
          },
          "end_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$",
            "description": "Appointment end as an ISO-8601 offset-aware datetime. Must be strictly after `start_at` (otherwise a 422).",
            "example": "2026-06-02T15:50:00.000Z"
          },
          "session_type": {
            "type": "string",
            "enum": [
              "virtual",
              "in_person"
            ],
            "description": "Session delivery mode. `virtual` provisions a video session and returns its join link on `patient_video_url`; `in_person` does not.",
            "example": "virtual"
          },
          "timezone": {
            "description": "Optional IANA timezone (e.g. `America/New_York`) for rendering `start_at` / `end_at` in local wall-clock time. Validated against the IANA timezone database when present, so an unknown zone is a 422. Aliases such as `Asia/Calcutta` and the bare `UTC` are accepted; a numeric UTC offset such as `+05:30` is not a named zone and is rejected. When omitted or `null`, the appointment inherits the booking clinic timezone.",
            "example": "America/New_York",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "notes": {
            "description": "Optional free-text appointment notes (up to 5000 characters). May contain protected health information. Omit or send `null` when none.",
            "example": "Initial intake session.",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 5000
              },
              {
                "type": "null"
              }
            ]
          },
          "notify_patient": {
            "default": false,
            "description": "When `true`, the patient receives a booking-confirmation email. Defaults to `false`. This flag is not stored on the appointment; the clinician confirmation and calendar updates happen regardless.",
            "example": false,
            "type": "boolean"
          },
          "notify_practitioner": {
            "default": false,
            "description": "When `true`, the clinician receives a booking-confirmation email carrying their own (host) join link. Defaults to `false`. This flag is not stored on the appointment.",
            "example": false,
            "type": "boolean"
          }
        },
        "required": [
          "partner_clinic_id",
          "partner_practitioner_id",
          "partner_patient_id",
          "start_at",
          "end_at",
          "session_type"
        ],
        "additionalProperties": false,
        "title": "PartnerAppointmentCreateBody",
        "description": "Body of `POST /v1/partner/appointments`. Books a real appointment that the clinician sees immediately. The practitioner and patient must both belong to `partner_clinic_id`, otherwise a 422 (`same-clinic-mismatch`). Conflicts are checked only against appointments your organization has already booked for this practitioner and return a 409; appointments booked elsewhere are not considered. Unknown keys are rejected. `notes` may contain protected health information, and `notify_patient` is a non-persisted behavior flag.",
        "example": {
          "partner_clinic_id": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "partner_practitioner_id": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
          "partner_patient_id": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
          "start_at": "2026-06-02T15:00:00.000Z",
          "end_at": "2026-06-02T15:50:00.000Z",
          "session_type": "virtual",
          "timezone": "America/New_York",
          "notes": "Initial intake session.",
          "notify_patient": false
        }
      },
      "PartnerAppointmentVideoInput": {
        "type": "object",
        "properties": {
          "clinician_url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8192
          },
          "patient_url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8192
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          },
          "presentation": {
            "default": "inline",
            "type": "string",
            "enum": [
              "inline",
              "external"
            ]
          }
        },
        "required": [
          "clinician_url",
          "patient_url"
        ],
        "additionalProperties": false,
        "description": "Complete clinician and patient HTTPS video URLs from an approved origin. Required for every new virtual appointment after PlaySpace enables supplied video. Resubmitting replaces both URLs for the next join or explicit reload; ongoing calls do not switch. Optional expiry must cover the appointment end. PlaySpace audio recording is independent of provider video and remains subject to organization recording settings and clinician access."
      },
      "PartnerAppointmentUpdateBody": {
        "type": "object",
        "properties": {
          "video": {
            "description": "Complete replacement of both supplied video URLs. Required when changing the times of an appointment that already uses supplied video; omission preserves video on other edits. Video ownership cannot be changed or removed.",
            "$ref": "#/components/schemas/PartnerAppointmentVideoInput"
          },
          "start_at": {
            "format": "date-time",
            "description": "New appointment start as an ISO-8601 offset-aware datetime. Coupled with `end_at`: supply both together to reschedule, and supplying exactly one is a 422. When supplied, must be strictly before `end_at`. Omit both to leave the window unchanged.",
            "example": "2026-06-02T16:00:00.000Z",
            "type": "string",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          },
          "end_at": {
            "format": "date-time",
            "description": "New appointment end as an ISO-8601 offset-aware datetime. Coupled with `start_at`: supply both together, and supplying exactly one is a 422. Must be strictly after `start_at`. Omit both to leave the window unchanged.",
            "example": "2026-06-02T16:50:00.000Z",
            "type": "string",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          },
          "timezone": {
            "description": "IANA timezone (e.g. `America/New_York`) for rendering `start_at` / `end_at` in local wall-clock time. Validated against the IANA timezone database when present, so an unknown zone is a 422. Aliases such as `Asia/Calcutta` and the bare `UTC` are accepted; a numeric UTC offset such as `+05:30` is not a named zone and is rejected. Send `null` to clear it (the appointment then renders against the booking clinic timezone); omit to leave it unchanged.",
            "example": "America/New_York",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "notes": {
            "description": "Free-text appointment notes (up to 5000 characters). May contain protected health information. Send `null` to clear it; omit to leave it unchanged.",
            "example": "Rescheduled at patient request.",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 5000
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "description": "New appointment status, one of `scheduled`, `completed`, `cancelled`, or `no_show`. Setting `cancelled` is the full cancellation path: it removes the appointment from the practitioner's connected calendars and, when `notify_patient` is `true`, emails the patient a cancellation. A cancelled appointment then drops from the default list (surface it with `include_deleted=true`), and any later `GET` or `PATCH` of that id returns a 404. Omit to leave the status unchanged.",
            "example": "completed",
            "type": "string",
            "enum": [
              "scheduled",
              "completed",
              "cancelled",
              "no_show"
            ]
          },
          "notify_patient": {
            "default": false,
            "description": "When `true`, the patient receives a reschedule or status-change notification email. Defaults to `false`. This flag is not stored on the appointment and is not a substantive change on its own, so a body carrying only `notify_patient` is rejected with a 422.",
            "example": false,
            "type": "boolean"
          },
          "notify_practitioner": {
            "default": false,
            "description": "When `true`, the clinician receives a reschedule, status-change, or cancellation notification email (a reschedule carries their own join link). Defaults to `false`. Not stored, and not a substantive change on its own, so a body carrying only `notify_practitioner` is rejected with a 422.",
            "example": false,
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "title": "PartnerAppointmentUpdateBody",
        "description": "Partial-update body for `PATCH /v1/partner/appointments/{id}` (reschedule, change status, edit notes, or cancel). All fields are optional, but at least one substantive field is required — the coupled `start_at` / `end_at` pair, `timezone`, `notes`, or `status` (`notify_patient` alone is not substantive). `start_at` and `end_at` are coupled (both or neither; sending one is a 422), and when both present `end_at` must be strictly after `start_at`. Setting `status` to `cancelled` is the full cancellation path, which drops the appointment from the default list and returns a 404 on a later `GET` or `PATCH`. `session_type` and the scoping ids cannot be patched, and unknown keys are rejected. `notes` may contain protected health information, and `notify_patient` is a non-persisted behavior flag.",
        "example": {
          "start_at": "2026-06-02T16:00:00.000Z",
          "end_at": "2026-06-02T16:50:00.000Z",
          "notes": "Rescheduled at patient request.",
          "notify_patient": false
        }
      },
      "ClinicCommunityItemType": {
        "type": "string",
        "enum": [
          "worksheet",
          "storybook",
          "form"
        ],
        "description": "Which kind of content a clinic-community item is. Games are not shared through this API.",
        "example": "worksheet"
      },
      "ClinicCommunityPublishBody": {
        "type": "object",
        "properties": {
          "item_type": {
            "$ref": "#/components/schemas/ClinicCommunityItemType"
          },
          "item_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "ID of the worksheet, storybook or form in the acting practitioner's own library, as returned by that resource's list endpoint.",
            "example": "a7b8c9d0-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Title colleagues will see. Defaults are not applied; send the title you want shown.",
            "example": "Feelings thermometer"
          },
          "description": {
            "description": "Optional note for colleagues about when to use this.",
            "example": "Scaling exercise for the first ten minutes of a session.",
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "age_categories": {
            "default": [],
            "description": "Who it suits. Any of `child`, `teen`, `adult`; empty when unspecified.",
            "example": [
              "child"
            ],
            "maxItems": 3,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "child",
                "teen",
                "adult"
              ]
            }
          },
          "tags": {
            "default": [],
            "description": "Free-text tags, up to 20 of up to 50 characters each.",
            "example": [
              "anxiety",
              "scaling"
            ],
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          }
        },
        "required": [
          "item_type",
          "item_id",
          "title"
        ],
        "additionalProperties": false,
        "title": "ClinicCommunityPublishBody",
        "description": "Which of your content to share with the clinic, and how to present it. Unknown fields are rejected."
      },
      "PartnerClinicCreateBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Clinic display name. Trimmed and non-empty, max 200 characters. Names are unique per partner and compared case-insensitively; a name already in use by another of your clinics returns 409 `clinic-name-conflict`.",
            "example": "Downtown Therapy Center"
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone identifier for the clinic (e.g. `America/New_York`). Validated against the IANA timezone database; an unknown or malformed zone (e.g. `America/New_Yrok`) is rejected with a 422. Aliases such as `Asia/Calcutta` and the bare `UTC` are accepted; a numeric UTC offset such as `+05:30` is not a named zone and is rejected. Applied to every practitioner created under this clinic.",
            "example": "America/New_York"
          }
        },
        "required": [
          "name",
          "timezone"
        ],
        "additionalProperties": false,
        "title": "PartnerClinicCreateBody",
        "description": "Body of `POST /v1/partner/clinics`: the clinic name and IANA timezone. Unknown keys are rejected.",
        "example": {
          "name": "Downtown Therapy Center",
          "timezone": "America/New_York"
        }
      },
      "PartnerClinicUpdateBody": {
        "type": "object",
        "properties": {
          "name": {
            "description": "Clinic display name. When supplied, trimmed and non-empty, max 200 characters; omit to leave unchanged. Setting it to the clinic's current name is a 200 no-op. Renaming to a name already in use by another of your clinics returns 409 `clinic-name-conflict` (names are unique per partner, compared case-insensitively).",
            "example": "Uptown Therapy Center",
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "timezone": {
            "description": "IANA timezone identifier for the clinic (e.g. `America/Chicago`). When supplied, validated against the IANA timezone database; an unknown or malformed zone (e.g. `America/New_Yrok`) is rejected with a 422. Aliases such as `Asia/Calcutta` and the bare `UTC` are accepted; a numeric UTC offset such as `+05:30` is not a named zone and is rejected. Omit to leave unchanged. Setting it to the clinic's current timezone is a 200 no-op.",
            "example": "America/Chicago",
            "type": "string"
          }
        },
        "additionalProperties": false,
        "title": "PartnerClinicUpdateBody",
        "description": "Partial-update body for `PATCH /v1/partner/clinics/{id}`. At least one of `name`, `timezone` is required. Unknown keys are rejected. Setting a field to its current value is a 200 no-op.",
        "example": {
          "name": "Uptown Therapy Center",
          "timezone": "America/Chicago"
        }
      },
      "PartnerEmbedTokenCreateBody": {
        "type": "object",
        "properties": {
          "capabilities": {
            "minItems": 1,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmbedCapability"
            },
            "description": "Actions to grant the embedded surface. Request the narrowest set the screen needs — a read-only viewer should not carry storybook:create.",
            "example": [
              "storybook:read",
              "storybook:create"
            ]
          },
          "origins": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Absolute origins permitted to frame the embed, e.g. https://app.yourclinic.com. Becomes the frame-ancestors directive on the embed document; a host whose origin is not listed cannot render the iframe at all. A wildcard is accepted only as a whole leading label (https://*.yourclinic.com); a wildcard inside a label (https://app-*.yourclinic.com) is not a valid CSP source expression and is rejected here, because a browser would silently discard it and refuse to frame the embed from anywhere.",
            "example": [
              "https://app.yourclinic.com"
            ]
          },
          "patient_id": {
            "format": "uuid",
            "description": "Patient the embedded surface acts on, as the PlaySpace patient id returned by /v1/partner/patients. REQUIRED when capabilities include form:submit, form:send, storybook:share or worksheet:share: a form submission is patient data and must have a subject, and a send reads that patient's address and records a pending submission against them. The patient must belong to your organization AND be on the delegated practitioner's roster; a patient outside either is rejected with 403, not silently ignored. Omit it for surfaces that only read.",
            "example": "3f1b0e2a-9c4d-4f4b-8a1e-2d6c5b7a9e01",
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "ttl_seconds": {
            "description": "Lifetime of the returned token in seconds. Defaults to 900 (15 minutes) and is capped at 3600. Mint one per page load rather than reusing a long-lived token.",
            "example": 900,
            "type": "integer",
            "minimum": 60,
            "maximum": 3600
          }
        },
        "required": [
          "capabilities",
          "origins"
        ],
        "title": "PartnerEmbedTokenCreateBody",
        "description": "Request body for POST /v1/partner/embed-tokens. The practitioner is NOT specified here — it is taken from the delegated token, so a caller cannot mint a token acting as someone it was not delegated to. The patient, by contrast, IS named here: one clinician sees many patients, so there is nothing in the delegated token to infer it from."
      },
      "EmbedCapability": {
        "type": "string",
        "enum": [
          "storybook:read",
          "storybook:create",
          "storybook:write",
          "storybook:delete",
          "form:read",
          "form:create",
          "form:submit",
          "form:write",
          "form:delete",
          "form:compose",
          "form:send",
          "worksheet:read",
          "worksheet:create",
          "worksheet:write",
          "worksheet:delete",
          "worksheet:generate",
          "games:play",
          "note:read",
          "note:write",
          "client:read",
          "appointment:read",
          "appointment:write",
          "shell:read",
          "playroom:read",
          "playroom:write",
          "session:launch",
          "data:export",
          "storybook:share",
          "worksheet:share",
          "community:read",
          "community:write"
        ],
        "description": "A single action the embedded surface may perform. Capabilities are checked per request by the embed API tier; a token without storybook:create cannot start a generation, one without form:create cannot author a new form template, one without form:submit can render a form but not save an answer, one without worksheet:read cannot open a worksheet, one without worksheet:create cannot upload a PDF, start a blank worksheet or duplicate one, one without worksheet:write cannot edit an existing worksheet, one without worksheet:delete cannot remove one, and one without worksheet:generate cannot ask the image generator for artwork to place on a page. The four worksheet verbs are deliberately separate: worksheet:create ADDS a new worksheet (from a PDF, blank, or as a copy of one the clinician already has), worksheet:write CHANGES one that already exists — its pages, its publish state and where it sits on a shelf, including adding, reordering and removing pages — worksheet:delete REMOVES one from the library, and worksheet:generate is the only one that SPENDS MONEY, so a host can mint a drawing seat that cannot bill. storybook:write is the storybook sibling of that middle verb: it publishes and shelves a storybook that already exists, rewrites a page’s text, accepts a regenerated illustration and reorders the pages, and it neither starts a generation (storybook:create, which also covers the two paid edits — regenerating a page’s illustration and adding a page) nor opens one (storybook:read). storybook:delete removes a storybook from the library, a soft delete that also takes it off every room shelf it sat on; it is its own verb for the reason worksheet:delete is. Request only the verbs the screen actually needs. form:read covers a form template AND the responses recorded against it, because a submission is meaningless without the template it answers, so a token that could read one and not the other buys a surface that cannot render. form:write CHANGES a form that already exists — its name, its description and its questions — and form:delete RETIRES one from the library, a soft delete that also takes it off every room shelf it sat on and leaves every recorded response intact and readable. The three form authoring verbs are separate for the reason the worksheet verbs are: form:create ADDS a new template, form:write REWRITES an instrument a clinician’s patients may already have answered, and form:delete REMOVES one, so a host can mint a builder that cannot rewrite history and an editor that cannot destroy a library. Neither carries the authoring state: a form reaches published at creation or through the clinician’s own application, and no partner-facing tier can promote a draft. Neither publishes or shelves either. form:compose lets the clinician place a form on one of their own playroom or toolkit shelves from inside the frame, which is what makes it reachable during a live session; it can only place a form that is already shelf-ready, and it never creates or deletes a room. note:read opens the acting clinician’s own clinical notes read-only, list and detail, and it needs no patient_id because a note is the clinician’s record and the list spans every patient they have seen. note:write is the separately licensable write half of that same area, reachable only from inside the framed workspace: the clinician may edit the active version of one of their own notes, sign it — which locks a new version in place — and unlock one they hold. It is never implied by note:read, because a host that bought the clinical-notes area to read has not thereby bought one that can sign or unlock a clinical record, and the server-to-server note endpoints stay read-only whatever is minted. It needs no patient_id for the same reason note:read does not. shell:read frames the WHOLE PlaySpace workspace rather than one artifact surface: the clinician’s real navigation, header and feature gating. It grants no data of its own — every area inside the workspace is still gated by its own capability, so mint shell:read alongside the areas the seat should reach, and a shell:read token with no note:read frames the product with the notes area refused. It is also refused outright on the patient seat of a game session. client:read and appointment:read open the acting clinician’s own client roster and appointment book, read-only, as areas inside that workspace; neither needs a patient_id, because both are lists spanning the clinician’s whole caseload rather than a record about one patient, and the appointment projection deliberately carries no meeting join link. client:read also covers a single client’s usage summary — counts and labels of what that client has been given and has returned, never the bodies. appointment:write books, reschedules and cancels an appointment in that clinician’s own calendar from inside the framed workspace; it is separate from appointment:read on purpose, because a host that bought a calendar to read has not thereby bought one that writes into a clinician’s book. An appointment booked through the frame always uses your organization’s platform video provider — a framed document holds no meeting URLs of yours, so the write accepts none and is never asked for any, whatever your supplied-video configuration says. It needs no patient_id: the row is a calendar entry on a caseload-wide seat, and the patient is named per request by partner-namespace id. form:send asks PlaySpace to email the bound patient a link to fill one of the clinician’s forms in: PlaySpace reads the address it holds for that patient, records the pending submission, mints the fill link and hands it to its email provider, so the link is never returned to you and no long-lived PlaySpace credential ever reaches your application. playroom:read lists the acting clinician’s own playrooms, opens one, and shows what is on its shelf; playroom:write creates a playroom and changes one — its name, description, room type, colour palette, tags, enabled session items and published state. Neither needs a patient_id, because a playroom is a room rather than a record about one patient. This tier has NO room delete and no attach or detach verb of its own: playroom:write cannot remove or restore a room, and taking an item off a shelf stays with worksheet:write, storybook:write and form:compose — but creating a room does place content on it, because the create back-fills the new room from the clinician’s own shelf-ready library. session:launch lets the clinician open a live session for one of their own appointments INSIDE the framed workspace, in place of your application opening a tab: the frame mints that clinician’s own join credential and renders the session in its content pane. It is separate from appointment:read on purpose — the calendar projection deliberately carries no meeting join link — and it needs no patient_id, because the subject is an appointment on a caseload-wide seat. It returns nothing to your application: the patient’s join link is never handed to a framed document, so mint that yourself from POST /v1/partner/appointments/{id}/session-links if you have a patient-facing surface of your own. data:export lets the clinician start a copy of their WHOLE PlaySpace record from inside the framed workspace — every client, with their notes, forms, worksheets, storybooks and uploaded files — watch it build, and download it as one ZIP file; it is the same archive POST /v1/partner/exports produces. Grant it deliberately: it is not implied by client:read or shell:read, and it needs no patient_id because the export spans the whole caseload. form:submit and form:send both additionally require patient_id — each is about one named patient, so the token must name them. storybook:share and worksheet:share show a Send to client button inside the reader or viewer that emails the named patient a secure link to that storybook or worksheet; both require patient_id for the same reason, and the frame never chooses a recipient. community:read shows the clinic community inside the framed workspace, the worksheets, storybooks and forms colleagues in the same clinic have shared, and lets the clinician preview one; community:write adds Share with clinic on the framed worksheet, storybook and form screens, Copy to my library and Remove in that area. Neither needs patient_id.",
        "example": "storybook:create"
      },
      "PartnerFormCreateBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Clinician-facing form name.",
            "example": "Intake questionnaire"
          },
          "description": {
            "description": "Optional longer description.",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ]
          },
          "fields": {
            "default": [],
            "description": "The form definition, in the same shape PlaySpace's own builder produces. Every field needs a unique `id` and a `type` the embedded surface can render — a duplicate id or an unsupported type is refused with a 422 naming the offenders, because a form saved with either would fail to open.",
            "type": "array",
            "items": {}
          },
          "status": {
            "default": "draft",
            "description": "Authoring lifecycle. Defaults to `draft`, which is deliberately invisible everywhere else on this API — a draft does not appear in the list, cannot be read by id, and cannot be attached to a shelf. Send `published` unless you intend to finish the form later in PlaySpace.",
            "example": "published",
            "type": "string",
            "enum": [
              "draft",
              "published"
            ]
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "title": "PartnerFormCreateBody",
        "description": "A new form owned by the practitioner the token is acting as."
      },
      "PartnerFormUpdateBody": {
        "type": "object",
        "properties": {
          "shelf_ready": {
            "type": "boolean",
            "const": true,
            "description": "Set to `true` to make this form eligible for a playroom or toolkit shelf, which is what an attach requires. Only `true` is accepted: withdrawing a form also removes it from every shelf it is on and cannot restore what it was before, so it is not offered as the opposite of this flag.",
            "example": true
          }
        },
        "required": [
          "shelf_ready"
        ],
        "additionalProperties": false,
        "title": "PartnerFormUpdateBody",
        "description": "What to change about a form. Today the only change on offer is making it shelf-eligible."
      },
      "PartnerGameType": {
        "type": "string",
        "enum": [
          "sandtray",
          "dollhouse"
        ],
        "description": "Which scene the session runs: a sandtray or a dollhouse. Fixed for the life of the session.",
        "example": "sandtray"
      },
      "PartnerGameSessionCreateBody": {
        "type": "object",
        "properties": {
          "game_type": {
            "$ref": "#/components/schemas/PartnerGameType"
          },
          "patient_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "The patient who will play, as one of your own patient ids. Scenes saved during the session are filed against this patient. The patient must be on the acting clinician's roster.",
            "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
          },
          "origins": {
            "minItems": 1,
            "maxItems": 8,
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Absolute origins permitted to frame either seat, e.g. https://app.yourclinic.com. Becomes the frame-ancestors directive on both embed documents; a host whose origin is not listed cannot render the iframe at all. A wildcard is accepted only as a whole leading label (https://*.yourclinic.com); a wildcard inside a label is not a valid source expression and is rejected here, because a browser would silently discard it and refuse to frame the embed from anywhere.",
            "example": [
              "https://app.yourclinic.com"
            ]
          },
          "game_session_id": {
            "format": "uuid",
            "description": "Omit to start a new session. Pass the `game_session_id` from an earlier response to re-issue fresh seat tokens for a session already in progress — required when refreshing tokens mid-session, because a frame refuses a token minted for a different session.",
            "example": "a3f1c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "token_ttl_seconds": {
            "description": "Lifetime of both returned tokens in seconds. Defaults to 900 (15 minutes) and is capped at 3600. A session outliving its tokens keeps working: re-mint and push a fresh token rather than reloading the frame.",
            "example": 900,
            "type": "integer",
            "minimum": 300,
            "maximum": 3600
          }
        },
        "required": [
          "game_type",
          "patient_id",
          "origins"
        ],
        "additionalProperties": false,
        "title": "PartnerGameSessionCreateBody",
        "description": "Which scene to run, for which patient, and which of your origins may frame it. The clinician is NOT specified here — it is taken from the delegated token, so a caller cannot start a session acting as someone it was not delegated to."
      },
      "PartnerPatientCreateBody": {
        "type": "object",
        "properties": {
          "partner_clinic_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "ID of the clinic this patient is created under; the patient is anchored to this clinic. Must reference one of your active clinics.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "created_by_partner_practitioner_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "ID of the practitioner credited as the creator of this patient. The patient is linked to this practitioner on creation. Must reference one of your active practitioners, in any clinic (not restricted to `partner_clinic_id`). Echoed back on the response.",
            "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
          },
          "first_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Patient given name. Trimmed; required non-empty; max 100 characters.",
            "example": "Avery"
          },
          "last_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Patient family name. Trimmed; required non-empty; max 100 characters.",
            "example": "Lee"
          },
          "preferred_name": {
            "description": "Optional patient preferred name. Trimmed when present; max 100 characters. Omit or send null when not recorded.",
            "example": "Alex",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ]
          },
          "date_of_birth": {
            "format": "date",
            "description": "Optional patient date of birth as an ISO-8601 calendar date (YYYY-MM-DD, no time component). A malformed or impossible date (e.g. 2026-02-30) or a value carrying a time component is a 422. Omit or send null when not recorded.",
            "example": "1998-03-14",
            "anyOf": [
              {
                "type": "string",
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              {
                "type": "null"
              }
            ]
          },
          "email": {
            "description": "Optional patient email. Trimmed and lower-cased; the canonical form is echoed back on the response. When supplied, must be a syntactically valid email address — a malformed value is a 422. No uniqueness check: a duplicate email is accepted (see the Patients tag). Omit or send null when not recorded.",
            "example": "avery.lee@example.com",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 254,
                "format": "email",
                "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "phone_number": {
            "description": "Optional patient phone number. Stored verbatim with no normalization; max 50 characters. The empty string is allowed and means \"no phone recorded\". Omit or send null when not recorded.",
            "example": "+1-555-0123",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 50
              },
              {
                "type": "null"
              }
            ]
          },
          "timezone": {
            "description": "Optional patient IANA timezone (e.g. America/New_York). When present, must be a valid IANA zone; an unknown or malformed zone is a 422. Aliases such as `Asia/Calcutta` and the bare `UTC` are accepted; a numeric UTC offset such as `+05:30` is not a named zone and is rejected. Omit or send null when not recorded.",
            "example": "America/New_York",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "partner_clinic_id",
          "created_by_partner_practitioner_id",
          "first_name",
          "last_name"
        ],
        "additionalProperties": false,
        "title": "PartnerPatientCreateBody",
        "description": "Body of POST /v1/partner/patients. `first_name`, `last_name`, `partner_clinic_id`, and `created_by_partner_practitioner_id` are required; `preferred_name`, `date_of_birth`, `email`, `phone_number`, and `timezone` are optional and nullable. Unknown keys are rejected with a 422. Email has no uniqueness check (see the Patients tag).",
        "example": {
          "partner_clinic_id": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "created_by_partner_practitioner_id": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
          "first_name": "Avery",
          "last_name": "Lee",
          "preferred_name": "Alex",
          "date_of_birth": "1998-03-14",
          "email": "avery.lee@example.com",
          "phone_number": "+1-555-0123",
          "timezone": "America/New_York"
        }
      },
      "PartnerPatientUpdateBody": {
        "type": "object",
        "properties": {
          "first_name": {
            "description": "Patient given name. When supplied, trimmed and required non-empty; max 100 characters. Omit to leave unchanged.",
            "example": "Avery",
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "last_name": {
            "description": "Patient family name. When supplied, trimmed and required non-empty; max 100 characters. Omit to leave unchanged.",
            "example": "Lee",
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "preferred_name": {
            "description": "Patient preferred name. Trimmed when present; max 100 characters. Send null to clear it, omit to leave unchanged.",
            "example": "Alex",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ]
          },
          "date_of_birth": {
            "format": "date",
            "description": "Patient date of birth as an ISO-8601 calendar date (YYYY-MM-DD, no time component). A malformed or impossible date (e.g. 2026-02-30) or a value carrying a time component is a 422. Send null to clear it, omit to leave unchanged.",
            "example": "1998-03-14",
            "anyOf": [
              {
                "type": "string",
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              {
                "type": "null"
              }
            ]
          },
          "email": {
            "description": "Patient email. Trimmed and lower-cased; the canonical form is echoed back on the response. When supplied, must be a syntactically valid email address — a malformed value is a 422. No uniqueness check: setting any value, including another patient's email, succeeds (see the Patients tag). Send null to clear it, omit to leave unchanged.",
            "example": "avery.lee@example.com",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 254,
                "format": "email",
                "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "phone_number": {
            "description": "Patient phone number. Stored verbatim with no normalization; max 50 characters. The empty string is allowed and means \"no phone recorded\". Send null to clear it, omit to leave unchanged.",
            "example": "+1-555-0123",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 50
              },
              {
                "type": "null"
              }
            ]
          },
          "timezone": {
            "description": "Patient IANA timezone (e.g. America/New_York). When present, must be a valid IANA zone; an unknown or malformed zone is a 422. Aliases such as `Asia/Calcutta` and the bare `UTC` are accepted; a numeric UTC offset such as `+05:30` is not a named zone and is rejected. Send null to clear it, omit to leave unchanged.",
            "example": "America/New_York",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false,
        "title": "PartnerPatientUpdateBody",
        "description": "Partial-update body for PATCH /v1/partner/patients/{id}. At least one of `first_name`, `last_name`, `preferred_name`, `date_of_birth`, `email`, `phone_number`, or `timezone` is required. Unknown keys are rejected with a 422. `partner_clinic_id` and `created_by_partner_practitioner_id` are not accepted — both are fixed at creation. Email has no uniqueness check (see the Patients tag).",
        "example": {
          "preferred_name": "Alex",
          "phone_number": "+1-555-0199"
        }
      },
      "PartnerPlayroomCreateBody": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Room name shown in the picker. Trimmed and non-empty, max 200 characters.",
            "example": "Calm Down Corner"
          },
          "description": {
            "description": "Optional note about when to use the room. Max 1000 characters.",
            "example": "For the first ten minutes of a session with younger clients.",
            "type": "string",
            "maxLength": 1000
          },
          "room_type": {
            "default": "adult",
            "description": "Age band the room art is pitched at. Defaults to `adult`.",
            "$ref": "#/components/schemas/PartnerPlayroomRoomType"
          },
          "color_palette": {
            "default": "color1",
            "description": "Colour theme for the room. Defaults to `color1`.",
            "$ref": "#/components/schemas/PartnerPlayroomColorPalette"
          },
          "session_items": {
            "description": "Items to enable inside the room. Omit to get the standard set (everything except wall posters). An empty array creates a room with no activities.",
            "example": [
              "activity_shelf",
              "whiteboard",
              "sand_tray"
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerSessionItem"
            }
          },
          "tags": {
            "description": "Optional labels, at most 20, each 1-50 characters. Defaults to none.",
            "example": [
              "anxiety"
            ],
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          },
          "published": {
            "default": true,
            "description": "Whether the room is immediately offered at session start. Defaults to true; send false to create it as a draft.",
            "example": true,
            "type": "boolean"
          }
        },
        "required": [
          "title"
        ],
        "additionalProperties": false,
        "title": "PartnerPlayroomCreateBody",
        "description": "Body of `POST /v1/partner/playrooms`. Only `title` is required; the rest fall back to the same defaults the PlaySpace app applies. Unknown keys are rejected.",
        "example": {
          "title": "Calm Down Corner",
          "room_type": "child",
          "session_items": [
            "activity_shelf",
            "whiteboard",
            "sand_tray"
          ],
          "published": true
        }
      },
      "PartnerPlayroomRoomType": {
        "type": "string",
        "enum": [
          "adult",
          "child",
          "teen"
        ],
        "title": "PartnerPlayroomRoomType",
        "description": "Age band the room art and copy are pitched at. Drives which themed artwork the room renders.",
        "example": "child"
      },
      "PartnerPlayroomColorPalette": {
        "type": "string",
        "enum": [
          "color1",
          "color2",
          "color3"
        ],
        "title": "PartnerPlayroomColorPalette",
        "description": "Which of the three colour themes the room is decorated with. Combined with `room_type` it selects the room artwork and thumbnail.",
        "example": "color1"
      },
      "PartnerSessionItem": {
        "type": "string",
        "enum": [
          "activity_shelf",
          "multiplayer_games",
          "single_player_games",
          "whiteboard",
          "sand_tray",
          "dollhouse"
        ],
        "title": "PartnerSessionItem",
        "description": "One interactive item that can be enabled inside a session space. `activity_shelf` holds worksheets, storybooks, and forms; `multiplayer_games` and `single_player_games` are the game libraries; `whiteboard`, `sand_tray`, and `dollhouse` are the standalone activities.",
        "example": "sand_tray"
      },
      "PartnerPlayroomUpdateBody": {
        "type": "object",
        "properties": {
          "title": {
            "description": "New room name. Trimmed and non-empty, max 200 characters.",
            "example": "Calm Down Corner",
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "description": "New note, or null to clear it. Max 1000 characters.",
            "example": null,
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000
              },
              {
                "type": "null"
              }
            ]
          },
          "room_type": {
            "description": "New age band. Changing it re-derives `thumbnail_url`.",
            "example": "teen",
            "$ref": "#/components/schemas/PartnerPlayroomRoomType"
          },
          "color_palette": {
            "description": "New colour theme. Changing it re-derives `thumbnail_url`.",
            "example": "color2",
            "$ref": "#/components/schemas/PartnerPlayroomColorPalette"
          },
          "session_items": {
            "description": "Replacement set of enabled items — the array replaces the current one entirely.",
            "example": [
              "activity_shelf",
              "sand_tray"
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerSessionItem"
            }
          },
          "tags": {
            "description": "Replacement labels — the array replaces the current ones entirely.",
            "example": [
              "anxiety",
              "intake"
            ],
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          },
          "published": {
            "description": "Publish (`true`) or unpublish (`false`) the room.",
            "example": false,
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "title": "PartnerPlayroomUpdateBody",
        "description": "Body of `PATCH /v1/partner/playrooms/{id}`. Any subset of the create fields; at least one is required. Array fields replace rather than merge. Unknown keys are rejected.",
        "example": {
          "title": "Calm Down Corner",
          "published": false
        }
      },
      "PartnerContentType": {
        "type": "string",
        "enum": [
          "worksheet",
          "storybook",
          "form"
        ],
        "title": "PartnerContentType",
        "description": "Kind of content a shelf entry holds. `worksheet`, `storybook` and `form` are the three members defined today. **This is an open vocabulary on the way out.** PlaySpace adds content types over time — games are the next one — and a new member will begin appearing as `content_type` on these responses without a new API version, a new path, or any other advance signal. **Your client MUST ignore any `content_type` value it does not recognise**: skip the item, or render it generically. A client that throws, discards the whole response, or fails its own schema validation on an unknown member will break the first time a clinician puts a new kind of content on a shelf. On the way in the vocabulary is closed: a `contentType` path segment outside the list above is rejected with `422`.",
        "example": "worksheet"
      },
      "PartnerPractitionerCreateBody": {
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Practitioner given name.",
            "example": "Jane"
          },
          "last_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Practitioner family name.",
            "example": "Doe"
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
            "description": "Practitioner work email, which becomes their login identity. Normalized to lowercase, and the canonical form is returned in the response. An email already in use by another practitioner returns a 409.",
            "example": "jane.doe@clinic.example"
          },
          "partner_clinic_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "Identifier of the clinic this practitioner belongs to. Must reference an active clinic you can access; otherwise the request fails validation with a 422 naming `partner_clinic_id`.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "role": {
            "description": "Optional membership role in the clinic: `member`, `admin`, or `owner`. Defaults to `member`. Informational metadata in this version; it does not affect API authorization.",
            "example": "member",
            "type": "string",
            "enum": [
              "member",
              "admin",
              "owner"
            ]
          },
          "country": {
            "description": "Optional country this practitioner works in, as an uppercase ISO 3166-1 alpha-2 code such as `US` or `AU`. Lower-case spellings and three-letter codes are rejected. Omit to leave it unset; a practitioner with no country is treated as outside every country-restricted feature.",
            "example": "US",
            "type": "string",
            "pattern": "^[A-Z]{2}$"
          }
        },
        "required": [
          "first_name",
          "last_name",
          "email",
          "partner_clinic_id"
        ],
        "additionalProperties": false,
        "title": "PartnerPractitionerCreateBody",
        "description": "Fields for creating a practitioner.",
        "example": {
          "first_name": "Jane",
          "last_name": "Doe",
          "email": "jane.doe@clinic.example",
          "partner_clinic_id": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "role": "member",
          "country": "US"
        }
      },
      "PartnerPractitionerUpdateBody": {
        "type": "object",
        "properties": {
          "first_name": {
            "description": "Practitioner given name. Omit to leave unchanged.",
            "example": "Sam",
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "last_name": {
            "description": "Practitioner family name. Omit to leave unchanged.",
            "example": "Taylor",
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "email": {
            "description": "Practitioner work email, which becomes their login identity. Normalized to lowercase, and the canonical form is returned in the response. Setting it to another practitioner's email returns a 409; setting it to the practitioner's own current email is a no-op. Omit to leave unchanged.",
            "example": "sam.taylor@example.com",
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
          },
          "role": {
            "description": "Membership role in the clinic: `member`, `admin`, or `owner`. Informational metadata in this version; it does not affect API authorization. Omit to leave unchanged.",
            "example": "admin",
            "type": "string",
            "enum": [
              "member",
              "admin",
              "owner"
            ]
          },
          "status": {
            "description": "Set to `disabled` to suspend the practitioner's per-clinician (delegated) access, or `active` to restore it. Disabling is reversible and keeps all records — the practitioner can be re-enabled at any time, and PlaySpace support can also restore access. Omit to leave unchanged.",
            "example": "disabled",
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "country": {
            "description": "The country this practitioner works in, as an uppercase ISO 3166-1 alpha-2 code such as `US` or `AU`. PlaySpace uses it to decide which region-restricted features this practitioner is offered, so setting it accurately changes what they can do. Lower-case spellings and three-letter codes are rejected. Send `null` to clear it, after which the practitioner is treated as outside every region-restricted feature. Omit to leave unchanged.",
            "example": "AU",
            "anyOf": [
              {
                "type": "string",
                "pattern": "^[A-Z]{2}$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false,
        "title": "PartnerPractitionerUpdateBody",
        "description": "Fields for updating a practitioner. Supply at least one of `first_name`, `last_name`, `email`, `role`, `status`, or `country`. A practitioner cannot be moved to a different clinic.",
        "example": {
          "first_name": "Sam",
          "email": "sam.taylor@example.com",
          "role": "admin",
          "country": "AU"
        }
      },
      "PartnerSessionConfigPatch": {
        "type": "object",
        "properties": {
          "enabled_features": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": "boolean"
            },
            "description": "Feature switches, keyed by feature name. Session keys — games, whiteboard, worksheets, storybook — control the in-session toolbar. The remaining keys control what your framed PlaySpace workspace offers, and can only take a feature away: setting one true never grants a feature your organization is not entitled to. A key you omit keeps whatever it had; an unrecognised key is rejected rather than ignored.",
            "example": {
              "clinical_notes": false,
              "storybook": false
            }
          },
          "allowed_content": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "toolkit_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"
                    }
                  },
                  "playroom_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"
                    }
                  },
                  "worksheet_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"
                    }
                  },
                  "game_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"
                    }
                  }
                },
                "description": "Optional narrowing of the default session catalog to the listed items. Every id must be a UUID; a malformed id is refused rather than dropped, because a partially applied allowlist would curate the catalog in a way you did not ask for. Omit a key to leave that part of the catalog whole.",
                "example": {
                  "game_ids": []
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "video_provider": {
            "type": "string",
            "enum": [
              "Whereby",
              "Dailyco",
              "EightxEight",
              "None"
            ],
            "description": "Which video vendor PlaySpace opens sessions with. 'None' means your organization runs video elsewhere: PlaySpace will not create a meeting for your appointments and will not return join links for them.",
            "example": "None"
          },
          "branding": {
            "description": "White-label branding for the session surface (display name, logo, colours).",
            "example": {
              "display_name": "Northwind Charts"
            },
            "anyOf": [
              {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              {
                "type": "null"
              }
            ]
          },
          "waiting_room_enabled": {
            "description": "Whether a patient waits for the clinician to admit them before the session opens.",
            "example": true,
            "type": "boolean"
          },
          "default_camera_on": {
            "description": "Whether cameras start on when a session opens.",
            "example": true,
            "type": "boolean"
          },
          "recording_enabled": {
            "description": "Whether sessions may be recorded.",
            "example": false,
            "type": "boolean"
          },
          "upgrade_enabled": {
            "description": "Whether your framed PlaySpace workspace offers the \"Upgrade to PlaySpace\" page to eligible clinic owners (the owner of a clinic, or its only practitioner, while the clinic has not finished upgrading). Off unless you turn it on.",
            "example": true,
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "title": "PartnerSessionConfigPatch",
        "description": "The configuration fields to change. Every field is optional; anything you omit is left exactly as it was."
      },
      "PartnerStorybookCreateBody": {
        "type": "object",
        "properties": {
          "prompt": {
            "type": "string",
            "minLength": 10,
            "maxLength": 500,
            "description": "What the story should be about, in the clinician's own words. Written into the book, so keep it to what the story needs.",
            "example": "A brave fox who learns to ask a grown-up for help when they feel worried"
          },
          "settings": {
            "$ref": "#/components/schemas/PartnerStorybookGenerationSettings"
          }
        },
        "required": [
          "prompt",
          "settings"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookCreateBody",
        "description": "What to generate a storybook about, and how it should read and look."
      },
      "PartnerStorybookGenerationSettings": {
        "type": "object",
        "properties": {
          "target_age": {
            "type": "string",
            "enum": [
              "1-3",
              "3-6",
              "6-12",
              "12-18",
              "18-40",
              "40-65",
              "65+"
            ],
            "description": "Age band the story is written for.",
            "example": "6-12"
          },
          "number_of_pages": {
            "type": "string",
            "enum": [
              "few",
              "short",
              "medium",
              "long"
            ],
            "description": "Roughly how long the book should be.",
            "example": "short"
          },
          "lines_per_page": {
            "type": "string",
            "enum": [
              "brief",
              "standard",
              "detailed",
              "extended"
            ],
            "description": "How much text sits on each page.",
            "example": "standard"
          },
          "style": {
            "type": "string",
            "enum": [
              "educational",
              "adventure",
              "emotional",
              "funny"
            ],
            "description": "Tone of the story.",
            "example": "emotional"
          },
          "image_style": {
            "type": "string",
            "enum": [
              "cartoon",
              "watercolor",
              "storybook",
              "modern",
              "anime"
            ],
            "description": "Look of the illustrations.",
            "example": "watercolor"
          },
          "include_character_names": {
            "description": "Names to use for the characters. Whatever you send appears in the finished book.",
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "target_age",
          "number_of_pages",
          "lines_per_page",
          "style",
          "image_style"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookGenerationSettings",
        "description": "How the story should be written and illustrated."
      },
      "PartnerStorybookUpdateBody": {
        "type": "object",
        "properties": {
          "shelf_ready": {
            "type": "boolean",
            "const": true,
            "description": "Set to `true` to make this storybook eligible for a playroom or toolkit shelf, which is what an attach requires. Only `true` is accepted: withdrawing a book also removes it from every shelf it is on and cannot restore what it was before, so it is not offered as the opposite of this flag.",
            "example": true
          }
        },
        "required": [
          "shelf_ready"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookUpdateBody",
        "description": "What to change about a storybook. Today the only change on offer is making it shelf-eligible."
      },
      "PartnerContentShareBody": {
        "type": "object",
        "properties": {
          "patient_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
            "format": "uuid",
            "description": "Patient to email the link to, as returned on the patients endpoints as `data[].id`. Must be on the acting practitioner's current roster.",
            "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
          },
          "expiry_seconds": {
            "description": "How long the emailed link stays usable, between 1 hour and 30 days. Defaults to 7 days.",
            "example": 604800,
            "type": "integer",
            "minimum": 3600,
            "maximum": 2592000
          },
          "message": {
            "description": "Optional note from the practitioner, included in the email above the link. Plain text, up to 500 characters.",
            "example": "We read this one together today. Have a look before our next session.",
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "patient_id"
        ],
        "additionalProperties": false,
        "title": "PartnerContentShareBody",
        "description": "Which patient to email a link to, how long the link should work, and an optional note. There is no recipient field: the address comes from the patient record."
      },
      "PartnerToolkitCreateBody": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Toolkit name shown in the picker. Trimmed and non-empty, max 200 characters.",
            "example": "Anxiety Toolkit"
          },
          "description": {
            "description": "Optional note about when to use the toolkit. Max 1000 characters.",
            "example": "Grounding activities for the middle of a session.",
            "type": "string",
            "maxLength": 1000
          },
          "session_items": {
            "description": "Items to enable in the toolkit. Omit to get the standard set. An empty array creates a toolkit with no activities.",
            "example": [
              "activity_shelf",
              "whiteboard"
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerSessionItem"
            }
          },
          "tags": {
            "description": "Optional labels, at most 20, each 1-50 characters. Defaults to none.",
            "example": [
              "anxiety"
            ],
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          },
          "published": {
            "default": true,
            "description": "Whether the toolkit is immediately offered at session start. Defaults to true; send false to create it as a draft.",
            "example": true,
            "type": "boolean"
          }
        },
        "required": [
          "title"
        ],
        "additionalProperties": false,
        "title": "PartnerToolkitCreateBody",
        "description": "Body of `POST /v1/partner/toolkits`. Only `title` is required; the rest fall back to the same defaults the PlaySpace app applies. Unknown keys are rejected.",
        "example": {
          "title": "Anxiety Toolkit",
          "session_items": [
            "activity_shelf",
            "whiteboard"
          ],
          "published": true
        }
      },
      "PartnerToolkitUpdateBody": {
        "type": "object",
        "properties": {
          "title": {
            "description": "New toolkit name. Trimmed and non-empty, max 200 characters.",
            "example": "Anxiety Toolkit",
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "description": "New note, or null to clear it. Max 1000 characters.",
            "example": null,
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000
              },
              {
                "type": "null"
              }
            ]
          },
          "session_items": {
            "description": "Replacement set of enabled items — the array replaces the current one entirely.",
            "example": [
              "activity_shelf",
              "sand_tray"
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerSessionItem"
            }
          },
          "tags": {
            "description": "Replacement labels — the array replaces the current ones entirely.",
            "example": [
              "anxiety",
              "intake"
            ],
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          },
          "published": {
            "description": "Publish (`true`) or unpublish (`false`) the toolkit.",
            "example": false,
            "type": "boolean"
          }
        },
        "additionalProperties": false,
        "title": "PartnerToolkitUpdateBody",
        "description": "Body of `PATCH /v1/partner/toolkits/{id}`. Any subset of the create fields; at least one is required. Array fields replace rather than merge. Unknown keys are rejected.",
        "example": {
          "title": "Anxiety Toolkit",
          "published": false
        }
      },
      "PartnerWorksheetCreateBody": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Title as it will appear in the clinician's library.",
            "example": "Feelings Thermometer"
          },
          "description": {
            "description": "Optional longer description. Returned on every read of this worksheet.",
            "example": "Scaling exercise used at the start of a session.",
            "type": "string",
            "maxLength": 2000
          },
          "category": {
            "description": "Optional free-text category to file the worksheet under.",
            "example": "Emotional regulation",
            "type": "string",
            "maxLength": 100
          },
          "tags": {
            "description": "Optional tags, up to 20.",
            "example": [
              "anxiety",
              "scaling"
            ],
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          },
          "pages": {
            "minItems": 1,
            "maxItems": 100,
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerWorksheetCreatePage"
            },
            "description": "The worksheet's pages, in reading order. At least one. The whole set travels in this one request, so keep the encoded body under roughly 4MB — split a long document into several worksheets rather than one oversized request."
          }
        },
        "required": [
          "title",
          "pages"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetCreateBody",
        "description": "A worksheet to create in the acting practitioner's library, with its page images."
      },
      "PartnerWorksheetCreatePage": {
        "type": "object",
        "properties": {
          "image_base64": {
            "type": "string",
            "minLength": 1,
            "description": "The page image, base64-encoded. PNG, JPEG, GIF or WebP — the format is detected from the bytes, so no separate type field is needed and a mislabelled one cannot mislead. Pages appear in the order given."
          }
        },
        "required": [
          "image_base64"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetCreatePage",
        "description": "One page image of a worksheet being created."
      },
      "PartnerAppointmentListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerAppointment"
            },
            "description": "Page of appointments."
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerAppointmentListResponse",
        "description": "Response envelope for `GET /v1/partner/appointments`. `data` is the page of appointments; `meta` carries the request id, generation time, and cursor pagination state."
      },
      "PartnerAppointment": {
        "type": "object",
        "properties": {
          "video": {
            "$ref": "#/components/schemas/PartnerAppointmentVideoMetadata"
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of this appointment. Stable for the lifetime of the appointment and used on every subsequent call.",
            "example": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b"
          },
          "partner_clinic_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of the clinic this appointment belongs to.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "partner_practitioner_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of the practitioner this appointment is scheduled with.",
            "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
          },
          "partner_patient_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of the patient this appointment is for.",
            "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
          },
          "start_at": {
            "type": "string",
            "format": "date-time",
            "description": "Appointment start as an ISO-8601 offset-aware datetime. This is an absolute instant; read the `timezone` field to render it as local wall-clock time.",
            "example": "2026-06-02T15:00:00.000Z"
          },
          "end_at": {
            "type": "string",
            "format": "date-time",
            "description": "Appointment end as an ISO-8601 offset-aware datetime. Always strictly after `start_at`.",
            "example": "2026-06-02T15:50:00.000Z"
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone identifier (e.g. `America/New_York`) used to render `start_at` / `end_at` as local wall-clock time. Defaults to `UTC` when none is set. On read, older appointments may occasionally carry a non-IANA value.",
            "example": "America/New_York"
          },
          "session_type": {
            "type": "string",
            "enum": [
              "virtual",
              "in_person"
            ],
            "description": "Session delivery mode: `virtual` (video) or `in_person`. A `virtual` appointment carries a patient join link on `patient_video_url`, unless your organization is configured with `video_provider` set to `None`.",
            "example": "virtual"
          },
          "patient_video_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient PlaySpace entry link, never a supplied provider URL. Null for in-person or unprovisioned appointments, and platform-video appointments when the organization selects None. Partner-video appointments keep both PlaySpace entry links even with None. This link grants session access; deliver it securely to the patient.",
            "example": "https://app.playspace.health/join/aB3dE6fG9hJ2kL5mN8pQ1r"
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "completed",
              "cancelled",
              "no_show"
            ],
            "description": "Appointment status, one of `scheduled`, `completed`, `cancelled`, or `no_show`. A late cancellation reports as `cancelled`; a non-billable session that took place reports as `completed`.",
            "example": "scheduled"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Free-text appointment notes, or `null` when none are set. May contain protected health information, so handle and store it accordingly.",
            "example": "Initial intake session."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp recording when this appointment was booked through the API.",
            "example": "2026-06-01T12:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp recording when this appointment was last updated.",
            "example": "2026-06-01T12:00:00.000Z"
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp recording when this appointment was cancelled, or `null` when active. Only returned when the request sets `include_deleted=true`; otherwise the field is omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "video",
          "id",
          "partner_clinic_id",
          "partner_practitioner_id",
          "partner_patient_id",
          "start_at",
          "end_at",
          "timezone",
          "session_type",
          "patient_video_url",
          "status",
          "notes",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerAppointment",
        "description": "An appointment linking one practitioner and one patient. `start_at` / `end_at` / `timezone` / `session_type` / `status` / `patient_video_url` describe the scheduled session; `created_at` / `updated_at` / `deleted_at` track when the appointment was booked, last changed, and cancelled. The `notes` field may contain protected health information.",
        "example": {
          "id": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b",
          "partner_clinic_id": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "partner_practitioner_id": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
          "partner_patient_id": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
          "start_at": "2026-06-02T15:00:00.000Z",
          "end_at": "2026-06-02T15:50:00.000Z",
          "timezone": "America/New_York",
          "session_type": "virtual",
          "patient_video_url": "https://app.playspace.health/join/aB3dE6fG9hJ2kL5mN8pQ1r",
          "status": "scheduled",
          "notes": "Initial intake session.",
          "created_at": "2026-06-01T12:00:00.000Z",
          "updated_at": "2026-06-01T12:00:00.000Z"
        }
      },
      "PartnerAppointmentVideoMetadata": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "ownership": {
                "type": "string",
                "const": "platform"
              }
            },
            "required": [
              "ownership"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "ownership": {
                "type": "string",
                "const": "partner"
              },
              "label": {
                "type": "string"
              },
              "presentation": {
                "type": "string",
                "enum": [
                  "inline",
                  "external"
                ]
              },
              "expires_at": {
                "type": "string"
              }
            },
            "required": [
              "ownership",
              "presentation"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Video ownership and display metadata. Supplied provider URLs are never returned on appointment reads; each participant receives only their own URL when joining PlaySpace.",
        "type": "object"
      },
      "EnvelopeMeta": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "minLength": 1,
            "description": "UUID for this request. Also echoed in the X-Request-Id response header."
          },
          "generated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the response was produced.",
            "example": "2026-05-22T15:30:00.000Z"
          },
          "pagination": {
            "description": "Present on list endpoints; omitted on single-resource endpoints.",
            "$ref": "#/components/schemas/PaginationMeta"
          }
        },
        "required": [
          "request_id",
          "generated_at"
        ],
        "additionalProperties": false,
        "title": "EnvelopeMeta",
        "description": "Standard meta block emitted in the success envelope on every 2xx response."
      },
      "PaginationMeta": {
        "type": "object",
        "properties": {
          "cursor": {
            "description": "Cursor used for the current page; omitted on the first page.",
            "type": "string"
          },
          "next_cursor": {
            "description": "Cursor for the next page, or null at the end of the list.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Page size requested by the caller, post-validation.",
            "example": 25
          },
          "has_more": {
            "type": "boolean",
            "description": "True if another page exists after this one."
          }
        },
        "required": [
          "limit",
          "has_more"
        ],
        "additionalProperties": false,
        "title": "PaginationMeta",
        "description": "Cursor-pagination metadata returned in meta.pagination for list endpoints."
      },
      "Problem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URI identifying the problem type. All partner-API problem types live under https://api.playspace.health/problems/<slug>. The URI is a stable identifier and is not guaranteed to resolve.",
            "example": "https://api.playspace.health/problems/unauthorized"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "description": "Short, human-readable summary of the problem type.",
            "example": "Unauthorized"
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599,
            "description": "HTTP status code for this occurrence.",
            "example": 401
          },
          "detail": {
            "description": "Human-readable explanation specific to this occurrence.",
            "type": "string"
          },
          "instance": {
            "description": "URI reference identifying the specific occurrence — typically the request path.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "status"
        ],
        "additionalProperties": {},
        "title": "Problem",
        "description": "RFC 9457 Problem Details object. Returned with content-type application/problem+json for every error response."
      },
      "PartnerAppointmentWriteResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The created or updated appointment.",
            "$ref": "#/components/schemas/PartnerAppointmentWrite"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerAppointmentWriteResponse",
        "description": "Response envelope for the appointment write responses (`POST` 201, `PATCH` 200). `data` is the created or updated appointment including `clinician_video_url`; `meta` carries the request id and generation time, with no pagination."
      },
      "PartnerAppointmentWrite": {
        "type": "object",
        "properties": {
          "video": {
            "$ref": "#/components/schemas/PartnerAppointmentVideoMetadata"
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of this appointment. Stable for the lifetime of the appointment and used on every subsequent call.",
            "example": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b"
          },
          "partner_clinic_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of the clinic this appointment belongs to.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "partner_practitioner_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of the practitioner this appointment is scheduled with.",
            "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
          },
          "partner_patient_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of the patient this appointment is for.",
            "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
          },
          "start_at": {
            "type": "string",
            "format": "date-time",
            "description": "Appointment start as an ISO-8601 offset-aware datetime. This is an absolute instant; read the `timezone` field to render it as local wall-clock time.",
            "example": "2026-06-02T15:00:00.000Z"
          },
          "end_at": {
            "type": "string",
            "format": "date-time",
            "description": "Appointment end as an ISO-8601 offset-aware datetime. Always strictly after `start_at`.",
            "example": "2026-06-02T15:50:00.000Z"
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone identifier (e.g. `America/New_York`) used to render `start_at` / `end_at` as local wall-clock time. Defaults to `UTC` when none is set. On read, older appointments may occasionally carry a non-IANA value.",
            "example": "America/New_York"
          },
          "session_type": {
            "type": "string",
            "enum": [
              "virtual",
              "in_person"
            ],
            "description": "Session delivery mode: `virtual` (video) or `in_person`. A `virtual` appointment carries a patient join link on `patient_video_url`, unless your organization is configured with `video_provider` set to `None`.",
            "example": "virtual"
          },
          "patient_video_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient PlaySpace entry link, never a supplied provider URL. Null for in-person or unprovisioned appointments, and platform-video appointments when the organization selects None. Partner-video appointments keep both PlaySpace entry links even with None. This link grants session access; deliver it securely to the patient.",
            "example": "https://app.playspace.health/join/aB3dE6fG9hJ2kL5mN8pQ1r"
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "completed",
              "cancelled",
              "no_show"
            ],
            "description": "Appointment status, one of `scheduled`, `completed`, `cancelled`, or `no_show`. A late cancellation reports as `cancelled`; a non-billable session that took place reports as `completed`.",
            "example": "scheduled"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Free-text appointment notes, or `null` when none are set. May contain protected health information, so handle and store it accordingly.",
            "example": "Initial intake session."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp recording when this appointment was booked through the API.",
            "example": "2026-06-01T12:00:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp recording when this appointment was last updated.",
            "example": "2026-06-01T12:00:00.000Z"
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp recording when this appointment was cancelled, or `null` when active. Only returned when the request sets `include_deleted=true`; otherwise the field is omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "clinician_video_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Clinician (host) join link for a virtual appointment, or `null` for in-person appointments and when no video session was provisioned. Like `patient_video_url` it grants session access (it redirects to a credential-bearing session URL), so treat it as sensitive and deliver it only to the assigned clinician over a secure channel. Returned only on create and reschedule responses.",
            "example": "https://app.playspace.health/join/Zq7wX4vT1sR8nM5kJ2hG0f"
          }
        },
        "required": [
          "video",
          "id",
          "partner_clinic_id",
          "partner_practitioner_id",
          "partner_patient_id",
          "start_at",
          "end_at",
          "timezone",
          "session_type",
          "patient_video_url",
          "status",
          "notes",
          "created_at",
          "updated_at",
          "clinician_video_url"
        ],
        "additionalProperties": false,
        "title": "PartnerAppointmentWrite",
        "description": "A created or updated appointment: identical to `PartnerAppointment` plus `clinician_video_url`, the practitioner's own join link for a virtual session. Returned only on create and reschedule responses, never on reads.",
        "example": {
          "id": "e2f3a4b5-6c7d-4e8f-9a0b-1c2d3e4f5a6b",
          "partner_clinic_id": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "partner_practitioner_id": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
          "partner_patient_id": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
          "start_at": "2026-06-02T15:00:00.000Z",
          "end_at": "2026-06-02T15:50:00.000Z",
          "timezone": "America/New_York",
          "session_type": "virtual",
          "patient_video_url": "https://app.playspace.health/join/aB3dE6fG9hJ2kL5mN8pQ1r",
          "clinician_video_url": "https://app.playspace.health/join/Zq7wX4vT1sR8nM5kJ2hG0f",
          "status": "scheduled",
          "notes": "Initial intake session.",
          "created_at": "2026-06-01T12:00:00.000Z",
          "updated_at": "2026-06-01T12:00:00.000Z"
        }
      },
      "EnvelopeMetaWithoutPagination": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "minLength": 1,
            "description": "UUID for this request. Also echoed in the X-Request-Id response header."
          },
          "generated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the response was produced.",
            "example": "2026-05-22T15:30:00.000Z"
          }
        },
        "required": [
          "request_id",
          "generated_at"
        ],
        "additionalProperties": false,
        "title": "EnvelopeMetaWithoutPagination",
        "description": "Meta block emitted in the success envelope on every 2xx single-resource response (GET-by-id, POST, PATCH, DELETE). No pagination field — pagination is reserved for list endpoints."
      },
      "PartnerAppointmentResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The appointment at the requested id.",
            "$ref": "#/components/schemas/PartnerAppointment"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerAppointmentResponse",
        "description": "Response envelope for the single-appointment read response (`GET` by id). `data` is a single appointment; `meta` carries the request id and generation time, with no pagination."
      },
      "PartnerAppointmentSessionLinksResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The join links for the appointment.",
            "$ref": "#/components/schemas/PartnerAppointmentSessionLinks"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerAppointmentSessionLinksResponse",
        "description": "Response envelope for `POST /v1/partner/appointments/{id}/session-links`. `data` carries `patient_video_url` / `clinician_video_url`; `meta` carries the request id and generation time, with no pagination."
      },
      "PartnerAppointmentSessionLinks": {
        "type": "object",
        "properties": {
          "patient_video_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient join link for the appointment; the same link on every call. Always `null` for an in-person appointment, where the second seat is never taken; for a virtual appointment, `null` only if the session could not be provisioned. Grants session access (it redirects to a credential-bearing session URL), so treat it as sensitive and deliver it to the patient over a secure channel.",
            "example": "https://app.playspace.health/join/aB3dE6fG9hJ2kL5mN8pQ1r"
          },
          "clinician_video_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Clinician (host) entry link for the appointment, the same on every call, for both virtual and in-person appointments. `null` only if the session could not be provisioned. Grants session access (it redirects to a credential-bearing session URL), so treat it as sensitive and deliver it only to the assigned clinician over a secure channel.",
            "example": "https://app.playspace.health/join/Zq7wX4vT1sR8nM5kJ2hG0f"
          },
          "video_provider": {
            "type": "string",
            "enum": [
              "Whereby",
              "Dailyco",
              "EightxEight",
              "None"
            ],
            "description": "Which video service these links open into. `None` means the session shows the playroom only and the links carry no video call — either because your organization supplies video separately, or because this appointment is in-person. Otherwise configured for your organization by PlaySpace.",
            "example": "None"
          },
          "waiting_room_enabled": {
            "type": "boolean",
            "description": "Whether the patient waits on a holding screen until the clinician admits them. Always `false` for an in-person appointment, which has no second seat to admit. Otherwise only applies when `video_provider` is `None`; the other services hold the patient in their own waiting room instead.",
            "example": true
          }
        },
        "required": [
          "patient_video_url",
          "clinician_video_url",
          "video_provider",
          "waiting_room_enabled"
        ],
        "additionalProperties": false,
        "title": "PartnerAppointmentSessionLinks",
        "description": "The role-scoped session entry links for an appointment, the same on every call: `patient_video_url` for the patient (virtual appointments only) and `clinician_video_url` for the assigned clinician, alongside what those links will open into.",
        "example": {
          "patient_video_url": "https://app.playspace.health/join/aB3dE6fG9hJ2kL5mN8pQ1r",
          "clinician_video_url": "https://app.playspace.health/join/Zq7wX4vT1sR8nM5kJ2hG0f",
          "video_provider": "None",
          "waiting_room_enabled": true
        }
      },
      "ClinicCommunityListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClinicCommunityItem"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "ClinicCommunityListResponse",
        "description": "Envelope for GET /v1/partner/clinic-community/items. meta.pagination carries the cursor block."
      },
      "ClinicCommunityItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Clinic-community item id. Use it to preview, copy or remove the item."
          },
          "item_type": {
            "$ref": "#/components/schemas/ClinicCommunityItemType"
          },
          "item_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The shared copy's own id in its resource. Read-only; the copy belongs to the clinic community, not to any library."
          },
          "title": {
            "type": "string",
            "description": "Title as shown to colleagues. Clinician-authored text.",
            "example": "Feelings thermometer"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Note for colleagues, or null. Clinician-authored text."
          },
          "age_categories": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "child",
                "teen",
                "adult"
              ]
            },
            "description": "Who it suits. Empty when unspecified, never null."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags. Empty when none, never null.",
            "example": [
              "anxiety"
            ]
          },
          "thumbnail_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Short-lived signed URL for the cover image, or null (forms have none). Expires; re-list rather than caching it."
          },
          "copy_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "How many times colleagues have copied this item into their own library.",
            "example": 2
          },
          "shared_by_practitioner_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              {
                "type": "null"
              }
            ],
            "description": "The practitioner who shared it, as their id on the practitioners endpoints, or null when the sharer is not on your roster."
          },
          "shared_by_me": {
            "type": "boolean",
            "description": "True when the acting practitioner shared this item. Always false on an organization token."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 instant the item was shared."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update instant."
          }
        },
        "required": [
          "id",
          "item_type",
          "item_id",
          "title",
          "description",
          "age_categories",
          "tags",
          "thumbnail_url",
          "copy_count",
          "shared_by_practitioner_id",
          "shared_by_me",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "ClinicCommunityItem",
        "description": "One item shared with the clinic."
      },
      "ClinicCommunityDetailResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ClinicCommunityItemDetail"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "ClinicCommunityDetailResponse",
        "description": "Envelope for a single clinic-community item with its content."
      },
      "ClinicCommunityItemDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Clinic-community item id. Use it to preview, copy or remove the item."
          },
          "item_type": {
            "$ref": "#/components/schemas/ClinicCommunityItemType"
          },
          "item_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The shared copy's own id in its resource. Read-only; the copy belongs to the clinic community, not to any library."
          },
          "title": {
            "type": "string",
            "description": "Title as shown to colleagues. Clinician-authored text.",
            "example": "Feelings thermometer"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Note for colleagues, or null. Clinician-authored text."
          },
          "age_categories": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "child",
                "teen",
                "adult"
              ]
            },
            "description": "Who it suits. Empty when unspecified, never null."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags. Empty when none, never null.",
            "example": [
              "anxiety"
            ]
          },
          "thumbnail_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Short-lived signed URL for the cover image, or null (forms have none). Expires; re-list rather than caching it."
          },
          "copy_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "How many times colleagues have copied this item into their own library.",
            "example": 2
          },
          "shared_by_practitioner_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              {
                "type": "null"
              }
            ],
            "description": "The practitioner who shared it, as their id on the practitioners endpoints, or null when the sharer is not on your roster."
          },
          "shared_by_me": {
            "type": "boolean",
            "description": "True when the acting practitioner shared this item. Always false on an organization token."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 instant the item was shared."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update instant."
          },
          "content": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PartnerWorksheetDetail"
              },
              {
                "$ref": "#/components/schemas/PartnerStorybookDetail"
              },
              {
                "$ref": "#/components/schemas/PartnerForm"
              }
            ],
            "description": "The shared content, in the same shape as the matching get-by-id endpoint for its kind, discriminated by `item_type`. Page text and page images are clinician-authored; render them, do not log them."
          }
        },
        "required": [
          "id",
          "item_type",
          "item_id",
          "title",
          "description",
          "age_categories",
          "tags",
          "thumbnail_url",
          "copy_count",
          "shared_by_practitioner_id",
          "shared_by_me",
          "created_at",
          "updated_at",
          "content"
        ],
        "additionalProperties": false,
        "title": "ClinicCommunityItemDetail",
        "description": "One shared item with its content."
      },
      "PartnerWorksheetDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace worksheet id. Use it to open the worksheet in an embedded viewer."
          },
          "title": {
            "type": "string",
            "description": "The worksheet's title as it appears in the clinician's library.",
            "example": "Feelings Thermometer"
          },
          "page_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Number of pages in the worksheet.",
            "example": 3
          },
          "shelf_ready": {
            "type": "boolean",
            "description": "Read-only and derived: true when this worksheet is ELIGIBLE to be placed on a playroom or toolkit shelf — that is, when an attach will accept this id. Eligibility is not placement: an eligible worksheet may be on no shelf at all, and a worksheet already on one stops being eligible if its publication state changes. Attaching an id whose `shelf_ready` is false is refused with a 422 carrying `reason: not_shelf_ready`, so read this before attaching rather than after.",
            "example": true
          },
          "thumbnail_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Short-lived signed URL for the cover image, or null when none exists. Expires — re-list rather than caching it."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The worksheet's description, or null when it has none. Clinician-authored library text.",
            "example": "Scaling exercise used at the start of a session."
          },
          "category": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Free-text category the worksheet is filed under, or null when it has none.",
            "example": "Emotional regulation"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags on the worksheet. Empty when it has none — never null.",
            "example": [
              "anxiety",
              "scaling"
            ]
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which the worksheet was deleted, or null when active. Returned only when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerWorksheetPage"
            },
            "description": "Pages in reading order. Empty when the worksheet has no pages."
          }
        },
        "required": [
          "id",
          "title",
          "page_count",
          "shelf_ready",
          "thumbnail_url",
          "created_at",
          "updated_at",
          "description",
          "category",
          "tags",
          "pages"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetDetail",
        "description": "One worksheet with its pages."
      },
      "PartnerWorksheetPage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Page id."
          },
          "position": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Reading order, starting at 1.",
            "example": 1
          },
          "text": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Text on this page, or null when the page carries only artwork. Clinician-authored content."
          },
          "image_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Short-lived signed URL for the page background, or null when the page has none or the link could not be issued. Expires — re-read rather than caching it. Anything drawn on top of the background is baked into the PDF, not into this image."
          }
        },
        "required": [
          "id",
          "position",
          "text",
          "image_url"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetPage",
        "description": "One page of a worksheet, in reading order."
      },
      "PartnerStorybookDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace storybook id. Use it to open the book in an embedded reader."
          },
          "title": {
            "type": "string",
            "description": "Model-generated title. May echo details from the clinician prompt — treat as patient-adjacent content.",
            "example": "Finn the Brave Fox Asks for Help"
          },
          "status": {
            "type": "string",
            "description": "Generation state. `ready` books are readable; `generating` ones are still being illustrated; `failed` ones will not complete.",
            "example": "ready"
          },
          "page_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Number of pages currently in the book.",
            "example": 4
          },
          "shelf_ready": {
            "type": "boolean",
            "description": "Read-only and derived: true when this storybook is ELIGIBLE to be placed on a playroom or toolkit shelf — that is, when an attach will accept this id. Eligibility is not placement: an eligible book may be on no shelf at all, and a book already on one stops being eligible if its publication state changes. Attaching an id whose `shelf_ready` is false is refused with a 422 carrying `reason: not_shelf_ready`, so read this before attaching rather than after.",
            "example": true
          },
          "thumbnail_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Short-lived signed URL for the cover image, or null when none has been rendered yet. Expires — re-list rather than caching it."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which the storybook was deleted, or null when active. Returned only when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerStorybookPage"
            },
            "description": "Pages in reading order. Empty while a book is still being generated, or when generation failed."
          }
        },
        "required": [
          "id",
          "title",
          "status",
          "page_count",
          "shelf_ready",
          "thumbnail_url",
          "created_at",
          "updated_at",
          "pages"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookDetail",
        "description": "One storybook with its pages."
      },
      "PartnerStorybookPage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Page id."
          },
          "position": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Reading order, starting at 1.",
            "example": 1
          },
          "text": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Story text on this page, or null when the page carries only an illustration. Written for one child — clinical content."
          },
          "image_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Short-lived signed URL for the illustration, or null when the page has none or the link could not be issued. Expires — re-read rather than caching it."
          }
        },
        "required": [
          "id",
          "position",
          "text",
          "image_url"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookPage",
        "description": "One page of a storybook, in reading order."
      },
      "PartnerForm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace form id. Pass it to the embedded form surface to open this form for filling."
          },
          "name": {
            "type": "string",
            "description": "Clinician-authored form name.",
            "example": "Intake questionnaire"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional longer description, or null when the author set none."
          },
          "embeddable": {
            "type": "boolean",
            "description": "Whether the embedded form surface can render this form. False when the form uses a field type the embed does not support yet (see unsupported_field_types) — the form is still listed so a host can explain the gap rather than silently omit it.",
            "example": true
          },
          "unsupported_field_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Distinct field types in this form that the embed cannot render, empty when embeddable is true. Present so a host can say WHICH capability is missing.",
            "example": [
              "signature"
            ]
          },
          "shelf_ready": {
            "type": "boolean",
            "description": "Read-only and derived: true when this form is ELIGIBLE to render on a playroom or toolkit shelf, so the clinician can also open it from inside a live PlaySpace session. Independent of appearing in this list: THIS list is gated on the authoring lifecycle (published, not draft), while shelf-readiness is gated on the clinician sharing the form to their playrooms and toolkits — and sharing is off by default, so a listed form that is not shelf-ready is the common case. Eligibility is not placement: actual placement is a separate membership row, written with PUT /v1/partner/{playrooms,toolkits}/{id}/content/form/{formId} exactly as for a worksheet or a storybook — an attach of a form whose shelf_ready is false is refused with 422 reason: not_shelf_ready, which is why reading this flag first is cheaper than discovering it through the failure. A host that only frames forms through the embed can ignore this; a host that coordinates with what happens in session should not assume a listed form is reachable there.",
            "example": false
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "embeddable",
          "unsupported_field_types",
          "shelf_ready",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerForm",
        "description": "One published form owned by the acting practitioner. The field definition itself is deliberately NOT returned here — it is large, it changes independently of this list, and the embedded surface fetches it directly with the capability token."
      },
      "ClinicCommunityCopyResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ClinicCommunityCopy"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "ClinicCommunityCopyResponse",
        "description": "Envelope for POST /v1/partner/clinic-community/items/{id}/copy."
      },
      "ClinicCommunityCopy": {
        "type": "object",
        "properties": {
          "item_type": {
            "$ref": "#/components/schemas/ClinicCommunityItemType"
          },
          "item_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The new copy's id in the acting practitioner's own library. Read it back on that resource's get-by-id endpoint."
          }
        },
        "required": [
          "item_type",
          "item_id"
        ],
        "additionalProperties": false,
        "title": "ClinicCommunityCopy",
        "description": "The library copy a copy request made."
      },
      "PartnerClinicListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerClinic"
            },
            "description": "Page of clinics."
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerClinicListResponse",
        "description": "Response envelope for `GET /v1/partner/clinics`. `data` is the page of clinics; `meta` carries the request id, timestamp, and cursor pagination state."
      },
      "PartnerClinic": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Stable UUID for the clinic. Use this id on every subsequent call that targets the clinic.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "name": {
            "type": "string",
            "description": "Clinic display name.",
            "example": "Downtown Therapy Center"
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone for the clinic. Applied to practitioners created under the clinic.",
            "example": "America/New_York"
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the clinic was created.",
            "example": "2026-05-22T15:30:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the clinic was last updated.",
            "example": "2026-05-22T15:30:00.000Z"
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which the clinic was de-listed, or null when active. Returned only when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name",
          "timezone",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerClinic",
        "description": "A clinic as seen by the partner: a stable id, display name, and timezone.",
        "example": {
          "id": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "name": "Downtown Therapy Center",
          "timezone": "America/New_York",
          "created_at": "2026-05-22T15:30:00.000Z",
          "updated_at": "2026-05-22T15:30:00.000Z"
        }
      },
      "PartnerClinicResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The clinic at the requested id.",
            "$ref": "#/components/schemas/PartnerClinic"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerClinicResponse",
        "description": "Response envelope for a single clinic. `data` is one clinic; `meta` carries the request id and timestamp, with no pagination."
      },
      "ClinicNotEmptyProblem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URI identifying the problem type. All partner-API problem types live under https://api.playspace.health/problems/<slug>. The URI is a stable identifier and is not guaranteed to resolve.",
            "example": "https://api.playspace.health/problems/unauthorized"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "description": "Short, human-readable summary of the problem type.",
            "example": "Unauthorized"
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599,
            "description": "HTTP status code for this occurrence.",
            "example": 401
          },
          "detail": {
            "description": "Human-readable explanation specific to this occurrence.",
            "type": "string"
          },
          "instance": {
            "description": "URI reference identifying the specific occurrence — typically the request path.",
            "type": "string"
          },
          "practitioner_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "Number of active practitioners still belonging to this clinic. The clinic cannot be deleted while this is greater than 0; clear or reassign the practitioners first.",
            "example": 2
          },
          "patient_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "Number of active patients still belonging to this clinic. At v1 this always reads 0; the key is reserved so it can begin blocking deletion without a breaking contract change.",
            "example": 0
          },
          "appointment_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "Number of active appointments still belonging to this clinic. At v1 this always reads 0; the key is reserved so it can begin blocking deletion without a breaking contract change.",
            "example": 0
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "practitioner_count",
          "patient_count",
          "appointment_count"
        ],
        "additionalProperties": {},
        "title": "ClinicNotEmptyProblem",
        "description": "Problem-details body for the `clinic-not-empty` 409 returned by DELETE /v1/partner/clinics/{id}. Extends the base Problem object with practitioner_count, patient_count, and appointment_count — non-negative integers naming how many dependents still belong to the clinic. At v1, patient_count and appointment_count always read 0; the keys are reserved so deletion can begin blocking on patients and appointments without a breaking contract change."
      },
      "PartnerEmbedTokenResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerEmbedToken"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerEmbedTokenResponse",
        "description": "Top-level envelope for POST /v1/partner/embed-tokens."
      },
      "PartnerEmbedToken": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "The embed capability token. Place it in the iframe URL. Treat as a credential: it is bearer-equivalent for the capabilities it names."
          },
          "expires_at": {
            "type": "string",
            "description": "ISO-8601 instant after which the token is rejected.",
            "example": "2026-08-12T15:45:00.000Z"
          },
          "practitioner_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace practitioner the embed will act as, resolved from the delegated token. Storybooks created in the frame are owned by this practitioner."
          },
          "partner_practitioner_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The partner-namespace practitioner id the token was delegated to — the identifier the partner already holds."
          },
          "patient_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              {
                "type": "null"
              }
            ],
            "description": "The patient the token is bound to, echoed back, or null when none was requested. This is the same PlaySpace patient id that was sent in — the internal client identifier it maps to is never returned."
          },
          "capabilities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmbedCapability"
            },
            "description": "Capabilities actually granted, echoed for confirmation."
          }
        },
        "required": [
          "token",
          "expires_at",
          "practitioner_id",
          "partner_practitioner_id",
          "patient_id",
          "capabilities"
        ],
        "additionalProperties": false,
        "title": "PartnerEmbedToken",
        "description": "A minted embed capability token and its binding."
      },
      "PartnerExportResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerExport"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerExportResponse",
        "description": "Top-level envelope for the export start and status endpoints. Both return this identical body."
      },
      "PartnerExport": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace export id. Poll and download using this."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "assembling",
              "ready",
              "failed",
              "expired"
            ],
            "description": "Where the export has got to. `queued`, `running` and `assembling` all mean \"keep waiting\". `ready` means the archive can be downloaded. `failed` means it will never be downloadable — read `failed_step` and `error_message`, then start a new one. `expired` means it finished but its files have since been deleted.",
            "example": "queued"
          },
          "counts": {
            "$ref": "#/components/schemas/PartnerExportCounts"
          },
          "archive": {
            "$ref": "#/components/schemas/PartnerExportArchive"
          },
          "expires_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "ISO-8601 instant after which the archive is deleted and the download stops working — seven days after it became ready. Null while the export is still running, because nothing has been published yet.",
            "example": "2026-09-16T09:14:22.000Z"
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 instant the export was requested.",
            "example": "2026-09-09T09:14:22.000Z"
          },
          "failed_step": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Which phase failed, when the status is `failed`; null otherwise. A short internal phase name, useful to quote to support.",
            "example": null
          },
          "error_message": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "A one-sentence, human-readable reason the export failed, when the status is `failed`; null otherwise. Safe to show to the clinician — it never carries record content.",
            "example": null
          }
        },
        "required": [
          "id",
          "status",
          "counts",
          "archive",
          "expires_at",
          "created_at",
          "failed_step",
          "error_message"
        ],
        "additionalProperties": false,
        "title": "PartnerExport",
        "description": "One export of the acting clinician's records. The same shape is returned when an export is started and every time it is polled, so one parser covers both."
      },
      "PartnerExportCounts": {
        "type": "object",
        "properties": {
          "total_steps": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "How many phases this export will run in total. `0` until the export has worked out what it has to collect, which is why progress should be read as a fraction rather than as a countdown.",
            "example": 12
          },
          "completed_steps": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "How many of those phases have finished. Divide by `total_steps` for a progress percentage, and treat a `total_steps` of `0` as \"not known yet\" rather than as complete.",
            "example": 4
          },
          "table_rows": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "How many records were gathered into the archive. Zero until collection has run.",
            "example": 1840
          },
          "documents": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "How many documents — notes, forms, worksheets, storybooks, uploads — were rendered into the archive.",
            "example": 96
          },
          "skipped": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "How many items were deliberately left out. Every one of them is listed with a reason in the archive itself, so a non-zero value here is a pointer to that list rather than a failure.",
            "example": 2
          }
        },
        "required": [
          "total_steps",
          "completed_steps",
          "table_rows",
          "documents",
          "skipped"
        ],
        "additionalProperties": false,
        "title": "PartnerExportCounts",
        "description": "Progress and volume counters for one export. All five start at zero and only rise; none of them is a promise about the finished archive until the status is `ready`."
      },
      "PartnerExportArchive": {
        "type": "object",
        "properties": {
          "byte_size": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ],
            "description": "Size of the finished archive in bytes, or null before it has been packed. Check the file you download against this.",
            "example": 48211904
          },
          "sha256": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Lowercase hex SHA-256 of the finished archive, or null before it has been packed. This and `byte_size` are the only integrity claim the archive makes — verify both after downloading, because a truncated transfer is otherwise indistinguishable from a complete one.",
            "example": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
          }
        },
        "required": [
          "byte_size",
          "sha256"
        ],
        "additionalProperties": false,
        "title": "PartnerExportArchive",
        "description": "What the finished archive weighs and hashes to. Both fields are null until the export has been packed; the object itself is always present."
      },
      "PartnerFormListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerForm"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerFormListResponse",
        "description": "Top-level envelope for GET /v1/partner/forms. meta.pagination carries the cursor block."
      },
      "PartnerFormResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerForm"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerFormResponse",
        "description": "Top-level envelope for the single-form endpoints. No pagination block."
      },
      "PartnerFormSubmissionListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerFormSubmission"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerFormSubmissionListResponse",
        "description": "Top-level envelope for the submission list. meta.pagination carries the cursor block."
      },
      "PartnerFormSubmission": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace submission id."
          },
          "form_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The form this answers — always the id in the path."
          },
          "patient_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              {
                "type": "null"
              }
            ],
            "description": "YOUR patient id, mapped back from PlaySpace's internal one. Null when the submission was recorded without a patient attached, or when the person answering has never been registered through this API."
          },
          "data": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {},
            "description": "The answers, keyed by field id. THIS IS PATIENT-AUTHORED CLINICAL CONTENT — render it, do not log it, and do not put it anywhere your own error reporting will capture."
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "`completed` for a finished submission, `in_progress` for a saved draft. A draft is a real row and will appear here; check this before treating an answer set as final."
          },
          "submitted_by": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Who the platform recorded as the author: `client` when the patient filled it themselves in a live session, `practitioner` when a clinician recorded it — including through an embedded form, where the capability token authorises the clinician even if the patient is the one typing.",
            "example": "client"
          },
          "submission_method": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "`in_platform` or `sent_out`."
          },
          "submitted_at": {
            "type": "string",
            "description": "ISO-8601 submission timestamp."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          }
        },
        "required": [
          "id",
          "form_id",
          "patient_id",
          "data",
          "status",
          "submitted_by",
          "submission_method",
          "submitted_at",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerFormSubmission",
        "description": "One set of answers recorded against a form. Contains patient-authored clinical content."
      },
      "PartnerFormSubmissionResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerFormSubmission"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerFormSubmissionResponse",
        "description": "Top-level envelope for a single submission."
      },
      "PartnerGameSaveListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerGameSave"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerGameSaveListResponse",
        "description": "Top-level envelope for GET /v1/partner/game-saves. meta.pagination carries the cursor block."
      },
      "PartnerGameSave": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace id of the saved scene."
          },
          "game_type": {
            "$ref": "#/components/schemas/PartnerGameType"
          },
          "save_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Name the clinician gave the scene, or null when it was never named. Clinician free text — may reference the child.",
            "example": "Session 3 - the flood"
          },
          "patient_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              {
                "type": "null"
              }
            ],
            "description": "Your patient id for the child the scene belongs to. Null when the scene was built outside a patient context, or for a patient your organization did not create."
          },
          "has_thumbnail": {
            "type": "boolean",
            "description": "Whether a preview image exists for this scene. When true, fetch the bytes from the thumbnail endpoint; there is no image URL on this payload.",
            "example": false
          },
          "is_active": {
            "type": "boolean",
            "description": "False once the scene has been deleted. Deleted scenes appear only when you ask for them with `include_deleted=true`.",
            "example": true
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-save timestamp."
          }
        },
        "required": [
          "id",
          "game_type",
          "save_name",
          "patient_id",
          "has_thumbnail",
          "is_active",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerGameSave",
        "description": "One saved sandtray or dollhouse scene owned by the acting clinician."
      },
      "PartnerGameSaveDetailResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerGameSaveDetail"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerGameSaveDetailResponse",
        "description": "Top-level envelope for GET /v1/partner/game-saves/{id}."
      },
      "PartnerGameSaveDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace id of the saved scene."
          },
          "game_type": {
            "$ref": "#/components/schemas/PartnerGameType"
          },
          "save_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Name the clinician gave the scene, or null when it was never named. Clinician free text — may reference the child.",
            "example": "Session 3 - the flood"
          },
          "patient_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              {
                "type": "null"
              }
            ],
            "description": "Your patient id for the child the scene belongs to. Null when the scene was built outside a patient context, or for a patient your organization did not create."
          },
          "has_thumbnail": {
            "type": "boolean",
            "description": "Whether a preview image exists for this scene. When true, fetch the bytes from the thumbnail endpoint; there is no image URL on this payload.",
            "example": false
          },
          "is_active": {
            "type": "boolean",
            "description": "False once the scene has been deleted. Deleted scenes appear only when you ask for them with `include_deleted=true`.",
            "example": true
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-save timestamp."
          },
          "state_data": {
            "description": "The scene itself — every object, its position, and the surface it sits on. Shape is the scene format the PlaySpace bundle reads and writes; treat it as opaque unless you are rendering it. Around a megabyte, which is why it is only on this response."
          }
        },
        "required": [
          "id",
          "game_type",
          "save_name",
          "patient_id",
          "has_thumbnail",
          "is_active",
          "created_at",
          "updated_at",
          "state_data"
        ],
        "additionalProperties": false,
        "title": "PartnerGameSaveDetail",
        "description": "One saved scene, including its contents."
      },
      "PartnerGameSessionResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerGameSession"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerGameSessionResponse",
        "description": "Top-level envelope for POST /v1/partner/game-sessions."
      },
      "PartnerGameSession": {
        "type": "object",
        "properties": {
          "game_session_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Key both seats join on. Unguessable and single-use — anyone holding it plus a valid token is in the same live scene, so pass it only to the two participants."
          },
          "game_type": {
            "$ref": "#/components/schemas/PartnerGameType"
          },
          "practitioner": {
            "description": "The clinician seat. May start a new scene, load a previous one, save, rename and delete.",
            "$ref": "#/components/schemas/PartnerGameSessionSeat"
          },
          "patient": {
            "description": "The patient seat. Plays in the scene the clinician has chosen; cannot save, rename or delete.",
            "$ref": "#/components/schemas/PartnerGameSessionSeat"
          }
        },
        "required": [
          "game_session_id",
          "game_type",
          "practitioner",
          "patient"
        ],
        "additionalProperties": false,
        "title": "PartnerGameSession",
        "description": "A live sandtray or dollhouse session and both ways into it."
      },
      "PartnerGameSessionSeat": {
        "type": "object",
        "properties": {
          "embed_url": {
            "type": "string",
            "description": "Ready-to-frame URL for this seat, with the token already attached. Put it straight in an iframe src. Treat it as a credential: never log it or render it as text.",
            "example": "https://app.playspace.health/embed/games/sandtray?token=eyJhbGciOi..."
          },
          "token": {
            "type": "string",
            "description": "The embed token on its own, for hosts that build the frame URL themselves or refresh a live frame. Bearer-equivalent for this seat."
          },
          "expires_at": {
            "type": "string",
            "description": "ISO-8601 instant after which this seat's token is rejected.",
            "example": "2026-08-18T15:45:00.000Z"
          }
        },
        "required": [
          "embed_url",
          "token",
          "expires_at"
        ],
        "additionalProperties": false,
        "title": "PartnerGameSessionSeat",
        "description": "One participant's way into the session."
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Health"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "HealthResponse",
        "description": "Top-level envelope for GET /v1/partner/health. data carries the health payload; meta carries the standard request_id/generated_at envelope-meta block."
      },
      "Health": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "ok",
            "description": "Always the literal string \"ok\" when the endpoint returns 200.",
            "example": "ok"
          },
          "partner_organization_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "UUID of the partner organization resolved from the request token. Echoed back so the partner can confirm the token is bound to the expected tenant.",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef0123456789"
          },
          "checked_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the health check was evaluated.",
            "example": "2026-05-22T15:30:00.000Z"
          }
        },
        "required": [
          "status",
          "partner_organization_id",
          "checked_at"
        ],
        "additionalProperties": false,
        "title": "Health",
        "description": "Payload returned by GET /v1/partner/health when the partner token authenticates successfully."
      },
      "PartnerNoteListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/PartnerNoteDetail"
                },
                {
                  "$ref": "#/components/schemas/PartnerNote"
                }
              ]
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerNoteListResponse",
        "description": "Top-level envelope for GET /v1/partner/notes. meta.pagination carries the cursor block. Items carry `content` and `version_number` only when `include=content` was set."
      },
      "PartnerNoteDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace note id."
          },
          "title": {
            "type": "string",
            "description": "The note's title. Often derived from the appointment it belongs to, but for a note written without one it is derived from the opening words of the note itself — so treat it as clinical content, not as a label: render it, do not log it or put it in a URL.",
            "example": "Session note"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Clinician-authored subtitle for the note, or null when it has none.",
            "example": null
          },
          "locked": {
            "type": "boolean",
            "description": "True when the clinician has locked the note against further editing. Locking is a clinical act — a locked note is the finalised record of the session.",
            "example": false
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          },
          "partner_appointment_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The appointment this note belongs to, as `id` on your own appointment records, or null. Null does not mean the note is unattached: it means the note is not attached to an appointment you booked through this API — a note taken during an ad-hoc recording has no appointment at all.",
            "example": null
          },
          "partner_patient_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The patient the session was with, as `id` on your own patient records, or null when the note has no appointment of yours behind it. Read the patient record for identity — it is never repeated here.",
            "example": null
          },
          "partner_practitioner_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The clinician the appointment was booked for, as `id` on your own practitioner records. This is always the clinician your token is acting as, and is null only when the note has no appointment of yours behind it.",
            "example": null
          },
          "content": {
            "type": "string",
            "description": "The written body of the note, as the clinician most recently saved it. Empty when the note has never been written to. This is clinical content: render and store it as such, and never put it in a log line, a URL or an error message.",
            "example": ""
          },
          "version_number": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ],
            "description": "Which revision of the note `content` came from. Rises each time the clinician saves. Null when the note has never been written to.",
            "example": 1
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "locked",
          "created_at",
          "updated_at",
          "partner_appointment_id",
          "partner_patient_id",
          "partner_practitioner_id",
          "content",
          "version_number"
        ],
        "additionalProperties": false,
        "title": "PartnerNoteDetail",
        "description": "One clinical note with its written body."
      },
      "PartnerNote": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace note id."
          },
          "title": {
            "type": "string",
            "description": "The note's title. Often derived from the appointment it belongs to, but for a note written without one it is derived from the opening words of the note itself — so treat it as clinical content, not as a label: render it, do not log it or put it in a URL.",
            "example": "Session note"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Clinician-authored subtitle for the note, or null when it has none.",
            "example": null
          },
          "locked": {
            "type": "boolean",
            "description": "True when the clinician has locked the note against further editing. Locking is a clinical act — a locked note is the finalised record of the session.",
            "example": false
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          },
          "partner_appointment_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The appointment this note belongs to, as `id` on your own appointment records, or null. Null does not mean the note is unattached: it means the note is not attached to an appointment you booked through this API — a note taken during an ad-hoc recording has no appointment at all.",
            "example": null
          },
          "partner_patient_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The patient the session was with, as `id` on your own patient records, or null when the note has no appointment of yours behind it. Read the patient record for identity — it is never repeated here.",
            "example": null
          },
          "partner_practitioner_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The clinician the appointment was booked for, as `id` on your own practitioner records. This is always the clinician your token is acting as, and is null only when the note has no appointment of yours behind it.",
            "example": null
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "locked",
          "created_at",
          "updated_at",
          "partner_appointment_id",
          "partner_patient_id",
          "partner_practitioner_id"
        ],
        "additionalProperties": false,
        "title": "PartnerNote",
        "description": "One clinical note belonging to the practitioner the token is acting as, without its written body."
      },
      "PartnerNoteDetailResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerNoteWithTranscript"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerNoteDetailResponse",
        "description": "Top-level envelope for GET /v1/partner/notes/{id}. `data.transcript` is present only when `include=transcript` was set."
      },
      "PartnerNoteWithTranscript": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace note id."
          },
          "title": {
            "type": "string",
            "description": "The note's title. Often derived from the appointment it belongs to, but for a note written without one it is derived from the opening words of the note itself — so treat it as clinical content, not as a label: render it, do not log it or put it in a URL.",
            "example": "Session note"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Clinician-authored subtitle for the note, or null when it has none.",
            "example": null
          },
          "locked": {
            "type": "boolean",
            "description": "True when the clinician has locked the note against further editing. Locking is a clinical act — a locked note is the finalised record of the session.",
            "example": false
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          },
          "partner_appointment_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The appointment this note belongs to, as `id` on your own appointment records, or null. Null does not mean the note is unattached: it means the note is not attached to an appointment you booked through this API — a note taken during an ad-hoc recording has no appointment at all.",
            "example": null
          },
          "partner_patient_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The patient the session was with, as `id` on your own patient records, or null when the note has no appointment of yours behind it. Read the patient record for identity — it is never repeated here.",
            "example": null
          },
          "partner_practitioner_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The clinician the appointment was booked for, as `id` on your own practitioner records. This is always the clinician your token is acting as, and is null only when the note has no appointment of yours behind it.",
            "example": null
          },
          "content": {
            "type": "string",
            "description": "The written body of the note, as the clinician most recently saved it. Empty when the note has never been written to. This is clinical content: render and store it as such, and never put it in a log line, a URL or an error message.",
            "example": ""
          },
          "version_number": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ],
            "description": "Which revision of the note `content` came from. Rises each time the clinician saves. Null when the note has never been written to.",
            "example": 1
          },
          "transcript": {
            "description": "The verbatim session recording, in order, present only when you asked for it with `include=transcript`. Each entry is one speaker turn — every consecutive word from the same speaker, joined — not one word, so the array is readable as it stands and needs no re-assembly. Null means this note has no transcript: either the session was never recorded, or the note was written by hand, or the transcription pipeline has not finished — those are indistinguishable here, and none of them is an error. Still expect it to be large: turning words into turns removes rows, not speech, so a long session runs to hundreds of turns and hundreds of kilobytes of verbatim text.",
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PartnerNoteTranscriptSegment"
                }
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "locked",
          "created_at",
          "updated_at",
          "partner_appointment_id",
          "partner_patient_id",
          "partner_practitioner_id",
          "content",
          "version_number"
        ],
        "additionalProperties": false,
        "title": "PartnerNoteWithTranscript",
        "description": "One clinical note with its written body, and its verbatim session transcript when `include=transcript` was set."
      },
      "PartnerNoteTranscriptSegment": {
        "type": "object",
        "properties": {
          "speaker": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Who said this, as the diarisation labelled them — a name where the recording carried one, otherwise the raw speaker identifier the transcription assigned, and null when the segment could not be attributed to anyone. Labels are stable within one transcript and mean nothing across two.",
            "example": "Speaker 1"
          },
          "text": {
            "type": "string",
            "description": "What was said, verbatim. This is the most sensitive field on the whole API: it is the unedited speech of a therapy session, including everything the clinician chose not to write down. Render and store it as clinical content, never log it, and never put it in a URL.",
            "example": "ZTest spoken sentence."
          },
          "start_seconds": {
            "type": "number",
            "description": "Offset from the start of the recording, in seconds, at which this segment begins.",
            "example": 12.4
          },
          "end_seconds": {
            "type": "number",
            "description": "Offset from the start of the recording, in seconds, at which this segment ends. Equal to `start_seconds` when the source gave no end time.",
            "example": 15.1
          }
        },
        "required": [
          "speaker",
          "text",
          "start_seconds",
          "end_seconds"
        ],
        "additionalProperties": false,
        "title": "PartnerNoteTranscriptSegment",
        "description": "One continuous stretch of speech from one speaker in a session recording."
      },
      "PartnerPatientListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerPatient"
            },
            "description": "Page of patient records."
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerPatientListResponse",
        "description": "Envelope for GET /v1/partner/patients. `data` is the page of patients; `meta` carries the request id, generation time, and pagination state."
      },
      "PartnerPatient": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Stable identifier for this patient. Use it on every subsequent API call.",
            "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
          },
          "partner_clinic_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "ID of the clinic this patient belongs to.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "created_by_partner_practitioner_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "ID of the practitioner who created this patient record. Always present; set at creation and not changed afterward.",
            "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
          },
          "first_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient given name. May be null when not recorded.",
            "example": "Avery"
          },
          "last_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient family name. May be null when not recorded.",
            "example": "Lee"
          },
          "preferred_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The name the patient prefers to be addressed by, which often differs from their given name. Null when not recorded.",
            "example": "Alex"
          },
          "date_of_birth": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient date of birth as an ISO-8601 calendar date (YYYY-MM-DD, no time component). May be null when not recorded.",
            "example": "1998-03-14"
          },
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient email address. Returned as stored, which may include legacy values that do not match a strict email format. May be null when not recorded.",
            "example": "avery.lee@example.com"
          },
          "phone_number": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient phone number, returned verbatim with no normalization (E.164, formatted, and free-form values all occur). May be null when not recorded.",
            "example": "+1-555-0123"
          },
          "timezone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient IANA timezone (e.g. America/New_York), used by the appointments surface for slot offsets. Returned as stored, which may include legacy non-IANA values. May be null when not recorded.",
            "example": "America/New_York"
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which this patient was added to the partner roster.",
            "example": "2026-05-28T15:30:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which this patient record was last updated.",
            "example": "2026-05-28T15:30:00.000Z"
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which this patient was removed from the partner roster, or null when active. Only returned when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "partner_clinic_id",
          "created_by_partner_practitioner_id",
          "first_name",
          "last_name",
          "preferred_name",
          "date_of_birth",
          "email",
          "phone_number",
          "timezone",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerPatient",
        "description": "A patient record. Carries identity and contact fields that are PHI. Address and additional demographic fields are not included in this version and may be added later without a breaking change.",
        "example": {
          "id": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
          "partner_clinic_id": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "created_by_partner_practitioner_id": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
          "first_name": "Avery",
          "last_name": "Lee",
          "preferred_name": "Alex",
          "date_of_birth": "1998-03-14",
          "email": "avery.lee@example.com",
          "phone_number": "+1-555-0123",
          "timezone": "America/New_York",
          "created_at": "2026-05-28T15:30:00.000Z",
          "updated_at": "2026-05-28T15:30:00.000Z"
        }
      },
      "PartnerPatientCreateResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The newly created patient record. Does not include `linked_partner_practitioner_ids`.",
            "$ref": "#/components/schemas/PartnerPatient"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerPatientCreateResponse",
        "description": "Envelope for POST /v1/partner/patients (201). `data` is the newly created patient; `meta` carries the request id and generation time."
      },
      "PartnerPatientResponse": {
        "type": "object",
        "properties": {
          "data": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PartnerPatient"
              },
              {
                "$ref": "#/components/schemas/PartnerPatientWithLinkedPractitioners"
              }
            ],
            "description": "The patient record at the requested id. Carries the `linked_partner_practitioner_ids` field only when `include_linked_practitioners=true`."
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerPatientResponse",
        "description": "Envelope for GET /v1/partner/patients/{id}. `data` is a single patient record, optionally carrying `linked_partner_practitioner_ids`; `meta` carries the request id and generation time."
      },
      "PartnerPatientWithLinkedPractitioners": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Stable identifier for this patient. Use it on every subsequent API call.",
            "example": "d1c2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
          },
          "partner_clinic_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "ID of the clinic this patient belongs to.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "created_by_partner_practitioner_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "ID of the practitioner who created this patient record. Always present; set at creation and not changed afterward.",
            "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
          },
          "first_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient given name. May be null when not recorded.",
            "example": "Avery"
          },
          "last_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient family name. May be null when not recorded.",
            "example": "Lee"
          },
          "preferred_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The name the patient prefers to be addressed by, which often differs from their given name. Null when not recorded.",
            "example": "Alex"
          },
          "date_of_birth": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient date of birth as an ISO-8601 calendar date (YYYY-MM-DD, no time component). May be null when not recorded.",
            "example": "1998-03-14"
          },
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient email address. Returned as stored, which may include legacy values that do not match a strict email format. May be null when not recorded.",
            "example": "avery.lee@example.com"
          },
          "phone_number": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient phone number, returned verbatim with no normalization (E.164, formatted, and free-form values all occur). May be null when not recorded.",
            "example": "+1-555-0123"
          },
          "timezone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Patient IANA timezone (e.g. America/New_York), used by the appointments surface for slot offsets. Returned as stored, which may include legacy non-IANA values. May be null when not recorded.",
            "example": "America/New_York"
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which this patient was added to the partner roster.",
            "example": "2026-05-28T15:30:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which this patient record was last updated.",
            "example": "2026-05-28T15:30:00.000Z"
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which this patient was removed from the partner roster, or null when active. Only returned when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "linked_partner_practitioner_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "description": "IDs of the practitioners currently linked to this patient. Treat as an unordered set; an empty array means no current links. Present only when `include_linked_practitioners=true`.",
            "example": [
              "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
              "a6e7d5c3-2b3c-4d5e-9f0a-1b2c3d4e5f6a"
            ]
          }
        },
        "required": [
          "id",
          "partner_clinic_id",
          "created_by_partner_practitioner_id",
          "first_name",
          "last_name",
          "preferred_name",
          "date_of_birth",
          "email",
          "phone_number",
          "timezone",
          "created_at",
          "updated_at",
          "linked_partner_practitioner_ids"
        ],
        "additionalProperties": false,
        "title": "PartnerPatientWithLinkedPractitioners",
        "description": "A patient record with the optional `linked_partner_practitioner_ids` field. Returned by GET /v1/partner/patients/{id} when `include_linked_practitioners=true`."
      },
      "PartnerPatientUpdateResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The updated patient record. Does not include `linked_partner_practitioner_ids`.",
            "$ref": "#/components/schemas/PartnerPatient"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerPatientUpdateResponse",
        "description": "Envelope for PATCH /v1/partner/patients/{id} (200). `data` is the updated patient; `meta` carries the request id and generation time."
      },
      "PartnerPlayroomListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerPlayroom"
            },
            "description": "Page of playrooms."
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerPlayroomListResponse",
        "description": "Response envelope for `GET /v1/partner/playrooms`. `data` is the page of rooms; `meta` carries the request id, timestamp, and cursor pagination state."
      },
      "PartnerPlayroom": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace playroom id. Use it to preselect this room when starting a session, or to address the room on `GET/{id}` and `PATCH`.",
            "example": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
          },
          "title": {
            "type": "string",
            "description": "Clinician-supplied room name, shown in the room picker.",
            "example": "Calm Down Corner"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional longer note about when to use the room, or null.",
            "example": "For the first ten minutes of a session with younger clients."
          },
          "room_type": {
            "$ref": "#/components/schemas/PartnerPlayroomRoomType"
          },
          "color_palette": {
            "$ref": "#/components/schemas/PartnerPlayroomColorPalette"
          },
          "session_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerSessionItem"
            },
            "description": "Items enabled inside the room. An empty array is legitimate: it produces a room with no activities.",
            "example": [
              "activity_shelf",
              "whiteboard",
              "sand_tray"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Free-text labels the clinician filters their own rooms by.",
            "example": [
              "anxiety",
              "intake"
            ]
          },
          "published": {
            "type": "boolean",
            "description": "Whether the room is offered when starting a session. An unpublished room is a draft: it stays in the library but cannot be picked.",
            "example": true
          },
          "is_default": {
            "type": "boolean",
            "description": "True for the starter rooms PlaySpace creates with every new practitioner account. Read-only — you cannot create or clear a default room.",
            "example": false
          },
          "thumbnail_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Preview image for the room, derived from `room_type` and `color_palette`. Stable and safe to cache; relative paths resolve against the PlaySpace app origin.",
            "example": "/assets/playroom/thumbnails/Child_1.avif"
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the room was created.",
            "example": "2026-08-18T15:30:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the room was last updated.",
            "example": "2026-08-18T15:30:00.000Z"
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which the room was removed, or null when active. Returned only when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "room_type",
          "color_palette",
          "session_items",
          "tags",
          "published",
          "is_default",
          "thumbnail_url",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerPlayroom",
        "description": "One playroom owned by the acting practitioner.",
        "example": {
          "id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
          "title": "Calm Down Corner",
          "description": "For the first ten minutes of a session with younger clients.",
          "room_type": "child",
          "color_palette": "color1",
          "session_items": [
            "activity_shelf",
            "whiteboard",
            "sand_tray"
          ],
          "tags": [
            "anxiety"
          ],
          "published": true,
          "is_default": false,
          "thumbnail_url": "/assets/playroom/thumbnails/Child_1.avif",
          "created_at": "2026-08-18T15:30:00.000Z",
          "updated_at": "2026-08-18T15:30:00.000Z"
        }
      },
      "PartnerPlayroomResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The playroom at the requested id.",
            "$ref": "#/components/schemas/PartnerPlayroom"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerPlayroomResponse",
        "description": "Response envelope for a single playroom. `data` is one room; `meta` carries the request id and timestamp, with no pagination."
      },
      "PartnerContentListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerContentItem"
            },
            "description": "Everything attached to this space, ordered by `order_index` where the combination has one and by attachment time otherwise. Empty when the space has no content on its shelf."
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerContentListResponse",
        "description": "Response envelope for the content of one playroom or toolkit. The whole shelf is returned in one response — there is no pagination."
      },
      "PartnerContentItem": {
        "type": "object",
        "properties": {
          "content_type": {
            "$ref": "#/components/schemas/PartnerContentType"
          },
          "content_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Id of the attached worksheet, storybook or form — the same id `GET /v1/partner/worksheets`, `GET /v1/partner/storybooks` and `GET /v1/partner/forms` return, and the id to use when attaching or detaching.",
            "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
          },
          "title": {
            "type": "string",
            "description": "Display title of the attached content, as it appears on the shelf. Clinician-authored or model-generated free text — treat it as patient-adjacent and keep it out of logs and analytics.",
            "example": "Feelings Thermometer"
          },
          "order_index": {
            "description": "Position on the shelf, lowest first, or null when the item was attached without one. The key is ABSENT for a worksheet on a playroom — the one combination of the six PlaySpace does not order, because that shelf has no ordering to store; those worksheets are returned in attachment order. It is present, though possibly null, for a storybook or a form on a playroom and for every content type on a toolkit.",
            "example": 0,
            "anyOf": [
              {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the content was attached to this space. This is the attachment time, not the time the worksheet, storybook or form itself was created.",
            "example": "2026-08-18T15:30:00.000Z"
          }
        },
        "required": [
          "content_type",
          "content_id",
          "title",
          "created_at"
        ],
        "additionalProperties": false,
        "title": "PartnerContentItem",
        "description": "One worksheet, storybook or form attached to a playroom or a toolkit. Identifies the content, not a copy of it — read the content itself through its own endpoint.",
        "example": {
          "content_type": "worksheet",
          "content_id": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70",
          "title": "Feelings Thermometer",
          "created_at": "2026-08-18T15:30:00.000Z"
        }
      },
      "PartnerContentItemResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The one attachment addressed by the request path.",
            "$ref": "#/components/schemas/PartnerContentItem"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerContentItemResponse",
        "description": "Response envelope for a single attachment. `data` is one shelf item; `meta` carries the request id and timestamp, with no pagination."
      },
      "PartnerContentAttachProblem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URI identifying the problem type. All partner-API problem types live under https://api.playspace.health/problems/<slug>. The URI is a stable identifier and is not guaranteed to resolve.",
            "example": "https://api.playspace.health/problems/unauthorized"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "description": "Short, human-readable summary of the problem type.",
            "example": "Unauthorized"
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599,
            "description": "HTTP status code for this occurrence.",
            "example": 401
          },
          "detail": {
            "description": "Human-readable explanation specific to this occurrence.",
            "type": "string"
          },
          "instance": {
            "description": "URI reference identifying the specific occurrence — typically the request path.",
            "type": "string"
          },
          "reason": {
            "description": "Present only on the shelf-readiness failure. `not_shelf_ready` means the content exists and is yours, but its publication state is not the one the in-session shelf renders, so attaching it would create a shelf entry no session participant can see. Publish the content and retry; `shelf_ready` on the content list responses tells you in advance which ids an attach will accept.",
            "example": "not_shelf_ready",
            "type": "string",
            "enum": [
              "not_shelf_ready"
            ]
          }
        },
        "required": [
          "type",
          "title",
          "status"
        ],
        "additionalProperties": {},
        "title": "PartnerContentAttachProblem",
        "description": "Problem-details body for the 422 returned when attaching content. Extends the base Problem object with an optional `reason` discriminator, which carries `not_shelf_ready` when the attach was refused because the content is not in a shelf-renderable published state."
      },
      "PartnerPractitionerListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerPractitioner"
            },
            "description": "A page of practitioner records."
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerPractitionerListResponse",
        "description": "A page of practitioners with pagination metadata."
      },
      "PartnerPractitioner": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Stable identifier for this practitioner. Use it on every subsequent practitioner request.",
            "example": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b"
          },
          "partner_clinic_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Identifier of the clinic this practitioner belongs to.",
            "example": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f"
          },
          "role": {
            "type": "string",
            "enum": [
              "member",
              "admin",
              "owner"
            ],
            "description": "The practitioner's membership role in this clinic: `member` (default), `admin`, or `owner`. Informational metadata in this version; it does not affect API authorization.",
            "example": "member"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ],
            "description": "Whether per-clinician (delegated) access for this practitioner is currently allowed: `active` or `disabled`. While disabled, delegated tokens for this practitioner are rejected with a 403 `practitioner-disabled` problem. Disabling is reversible via PATCH and keeps all records; organization tokens are unaffected.",
            "example": "active"
          },
          "playspace_account_linked_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "ISO-8601 timestamp when this practitioner also gained a direct PlaySpace login through the clinic-upgrade flow, or `null`. Read-only. Your integration's access to the practitioner is unchanged by it.",
            "example": null
          },
          "playspace_login_status": {
            "type": "string",
            "enum": [
              "not_invited",
              "invited",
              "linked",
              "skipped",
              "expired"
            ],
            "description": "Where this practitioner stands with a direct PlaySpace login after a clinic upgrade. `not_invited`: no link has been sent. `invited`: a set-password link is live. `linked`: they sign in directly (same signal as `playspace_account_linked_at`). `skipped`: the link could not be sent, or the email already had a PlaySpace login. `expired`: the last link lapsed unused. Read-only; your integration's access is unchanged by it.",
            "example": "not_invited"
          },
          "first_name": {
            "type": "string",
            "description": "Practitioner given name.",
            "example": "Test"
          },
          "last_name": {
            "type": "string",
            "description": "Practitioner family name.",
            "example": "Practitioner"
          },
          "email": {
            "type": "string",
            "format": "email",
            "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
            "description": "Practitioner work email address, also used as their login identity.",
            "example": "practitioner-1@clinic.example"
          },
          "country": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The country this practitioner works in, as an uppercase ISO 3166-1 alpha-2 code, or `null` when it has not been set. Some PlaySpace features are only offered in certain countries, so a practitioner with no country set is treated as outside every country-restricted feature.",
            "example": "US"
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp when the practitioner record was created.",
            "example": "2026-05-22T15:30:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp when the practitioner record was last updated.",
            "example": "2026-05-22T15:30:00.000Z"
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp when the practitioner was removed, or `null` when active. Only present when the request sets `include_deleted=true`.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "partner_clinic_id",
          "role",
          "status",
          "playspace_account_linked_at",
          "playspace_login_status",
          "first_name",
          "last_name",
          "email",
          "country",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerPractitioner",
        "description": "A practitioner record, bound to a single clinic.",
        "example": {
          "id": "b7f8e6a4-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
          "partner_clinic_id": "c8e7d6b3-2a3b-4c5d-8e9f-0a1b2c3d4e5f",
          "role": "member",
          "status": "active",
          "playspace_account_linked_at": null,
          "playspace_login_status": "not_invited",
          "first_name": "Test",
          "last_name": "Practitioner",
          "email": "practitioner-1@clinic.example",
          "country": "US",
          "created_at": "2026-05-22T15:30:00.000Z",
          "updated_at": "2026-05-22T15:30:00.000Z"
        }
      },
      "PartnerPractitionerResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The requested practitioner record.",
            "$ref": "#/components/schemas/PartnerPractitioner"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerPractitionerResponse",
        "description": "A single practitioner record."
      },
      "PartnerSessionConfigResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerSessionConfig"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerSessionConfigResponse",
        "description": "Top-level envelope for the session-configuration endpoints."
      },
      "PartnerSessionConfig": {
        "type": "object",
        "properties": {
          "supplied_video": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "approved_origins": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "revision": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "enabled",
              "approved_origins",
              "revision"
            ],
            "additionalProperties": false,
            "description": "PlaySpace-managed supplied video policy. Read-only regardless of partner_editable_fields. When enabled, every new virtual appointment requires its own clinician and patient video URLs from an approved HTTPS origin. Existing appointments retain their video ownership."
          },
          "video_provider": {
            "type": "string",
            "enum": [
              "Whereby",
              "Dailyco",
              "EightxEight",
              "None"
            ],
            "description": "Which video vendor PlaySpace opens sessions with. 'None' means your organization runs video elsewhere: PlaySpace will not create a meeting for your appointments and will not return join links for them.",
            "example": "None"
          },
          "enabled_features": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": "boolean"
            },
            "description": "The feature switches this organization has set. A feature not listed here has no setting and behaves as normal.",
            "example": {
              "clinical_notes": false
            }
          },
          "allowed_content": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "toolkit_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"
                    }
                  },
                  "playroom_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"
                    }
                  },
                  "worksheet_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"
                    }
                  },
                  "game_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$"
                    }
                  }
                },
                "additionalProperties": false,
                "description": "Optional narrowing of the default session catalog to the listed items. Every id must be a UUID; a malformed id is refused rather than dropped, because a partially applied allowlist would curate the catalog in a way you did not ask for. Omit a key to leave that part of the catalog whole.",
                "example": {
                  "game_ids": []
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "The session catalog narrowing, or null when the whole default catalog is offered.",
            "example": null
          },
          "branding": {
            "anyOf": [
              {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              {
                "type": "null"
              }
            ],
            "description": "White-label branding, or null when none is set.",
            "example": null
          },
          "waiting_room_enabled": {
            "type": "boolean",
            "description": "Whether a patient waits for the clinician to admit them before the session opens.",
            "example": true
          },
          "default_camera_on": {
            "type": "boolean",
            "description": "Whether cameras start on when a session opens.",
            "example": true
          },
          "recording_enabled": {
            "type": "boolean",
            "description": "Whether sessions may be recorded.",
            "example": false
          },
          "upgrade_enabled": {
            "type": "boolean",
            "description": "Whether your framed PlaySpace workspace offers the \"Upgrade to PlaySpace\" page to eligible clinic owners (the owner of a clinic, or its only practitioner, while the clinic has not finished upgrading). Off unless you turn it on.",
            "example": false
          },
          "partner_editable_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The fields your organization may change through this API. Read-only, and empty by default — ask PlaySpace to open the ones you need. A PATCH of any field not listed here is refused with 403 and changes nothing.",
            "example": [
              "enabled_features",
              "video_provider"
            ]
          },
          "is_active": {
            "type": "boolean",
            "description": "Whether the PlaySpace session surface is switched on for your organization. Read-only.",
            "example": true
          },
          "updated_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "ISO-8601 timestamp of the last change, or null when the configuration has never been changed.",
            "example": "2026-09-03T05:10:46.000Z"
          }
        },
        "required": [
          "supplied_video",
          "video_provider",
          "enabled_features",
          "allowed_content",
          "branding",
          "waiting_room_enabled",
          "default_camera_on",
          "recording_enabled",
          "upgrade_enabled",
          "partner_editable_fields",
          "is_active",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerSessionConfig",
        "description": "Your organization's session and workspace configuration, as it stands now."
      },
      "PartnerStorybookListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerStorybook"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookListResponse",
        "description": "Top-level envelope for GET /v1/partner/storybooks. meta.pagination carries the cursor block."
      },
      "PartnerStorybook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace storybook id. Use it to open the book in an embedded reader."
          },
          "title": {
            "type": "string",
            "description": "Model-generated title. May echo details from the clinician prompt — treat as patient-adjacent content.",
            "example": "Finn the Brave Fox Asks for Help"
          },
          "status": {
            "type": "string",
            "description": "Generation state. `ready` books are readable; `generating` ones are still being illustrated; `failed` ones will not complete.",
            "example": "ready"
          },
          "page_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Number of pages currently in the book.",
            "example": 4
          },
          "shelf_ready": {
            "type": "boolean",
            "description": "Read-only and derived: true when this storybook is ELIGIBLE to be placed on a playroom or toolkit shelf — that is, when an attach will accept this id. Eligibility is not placement: an eligible book may be on no shelf at all, and a book already on one stops being eligible if its publication state changes. Attaching an id whose `shelf_ready` is false is refused with a 422 carrying `reason: not_shelf_ready`, so read this before attaching rather than after.",
            "example": true
          },
          "thumbnail_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Short-lived signed URL for the cover image, or null when none has been rendered yet. Expires — re-list rather than caching it."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which the storybook was deleted, or null when active. Returned only when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "title",
          "status",
          "page_count",
          "shelf_ready",
          "thumbnail_url",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybook",
        "description": "One storybook owned by the acting practitioner."
      },
      "PartnerStorybookDetailResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerStorybookDetail"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookDetailResponse",
        "description": "Top-level envelope for GET /v1/partner/storybooks/{id}."
      },
      "PartnerStorybookPagesResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerStorybookPage"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookPagesResponse",
        "description": "Top-level envelope for GET /v1/partner/storybooks/{id}/pages. Every page of the book, in reading order — a book is small enough that this is never paginated."
      },
      "PartnerStorybookPageResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerStorybookPage"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookPageResponse",
        "description": "Top-level envelope for GET /v1/partner/storybooks/{id}/pages/{pageId}."
      },
      "PartnerStorybookShareResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerStorybookShare"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookShareResponse",
        "description": "Top-level envelope for POST /v1/partner/storybooks/{id}/share."
      },
      "PartnerStorybookShare": {
        "type": "object",
        "properties": {
          "share_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Handle for this share, or null if one could not be recorded. The email is sent either way."
          },
          "storybook_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The storybook that was sent."
          },
          "patient_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The patient it was sent to."
          },
          "expires_at": {
            "type": "string",
            "description": "ISO-8601 instant after which the emailed link stops working.",
            "example": "2026-09-15T15:30:00.000Z"
          }
        },
        "required": [
          "share_id",
          "storybook_id",
          "patient_id",
          "expires_at"
        ],
        "additionalProperties": false,
        "title": "PartnerStorybookShare",
        "description": "Result of emailing a storybook to a patient. Carries no address and no link — both are known only to the recipient."
      },
      "PartnerToolkitListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerToolkit"
            },
            "description": "Page of toolkits."
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerToolkitListResponse",
        "description": "Response envelope for `GET /v1/partner/toolkits`. `data` is the page of toolkits; `meta` carries the request id, timestamp, and cursor pagination state."
      },
      "PartnerToolkit": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace toolkit id. Use it to preselect this toolkit when starting a session, or to address it on `GET/{id}` and `PATCH`.",
            "example": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70"
          },
          "title": {
            "type": "string",
            "description": "Clinician-supplied toolkit name, shown in the picker.",
            "example": "Anxiety Toolkit"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional longer note about when to use the toolkit, or null.",
            "example": "Grounding activities for the middle of a session."
          },
          "session_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerSessionItem"
            },
            "description": "Items enabled in the toolkit. An empty array is legitimate: it produces a toolkit with no activities.",
            "example": [
              "activity_shelf",
              "whiteboard"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Free-text labels the clinician filters their own toolkits by.",
            "example": [
              "anxiety"
            ]
          },
          "published": {
            "type": "boolean",
            "description": "Whether the toolkit is offered when starting a session. An unpublished toolkit is a draft: it stays in the library but cannot be picked.",
            "example": true
          },
          "is_default": {
            "type": "boolean",
            "description": "True for the starter toolkits PlaySpace creates with every new practitioner account. Read-only — you cannot create or clear a default toolkit.",
            "example": false
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the toolkit was created.",
            "example": "2026-08-18T15:30:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 timestamp at which the toolkit was last updated.",
            "example": "2026-08-18T15:30:00.000Z"
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which the toolkit was removed, or null when active. Returned only when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "session_items",
          "tags",
          "published",
          "is_default",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false,
        "title": "PartnerToolkit",
        "description": "One toolkit owned by the acting practitioner.",
        "example": {
          "id": "d4e5f6a7-8b9c-4d0e-9f1a-2b3c4d5e6f70",
          "title": "Anxiety Toolkit",
          "description": "Grounding activities for the middle of a session.",
          "session_items": [
            "activity_shelf",
            "whiteboard"
          ],
          "tags": [
            "anxiety"
          ],
          "published": true,
          "is_default": false,
          "created_at": "2026-08-18T15:30:00.000Z",
          "updated_at": "2026-08-18T15:30:00.000Z"
        }
      },
      "PartnerToolkitResponse": {
        "type": "object",
        "properties": {
          "data": {
            "description": "The toolkit at the requested id.",
            "$ref": "#/components/schemas/PartnerToolkit"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerToolkitResponse",
        "description": "Response envelope for a single toolkit. `data` is one toolkit; `meta` carries the request id and timestamp, with no pagination."
      },
      "PartnerWorksheetListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerWorksheet"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetListResponse",
        "description": "Top-level envelope for GET /v1/partner/worksheets. meta.pagination carries the cursor block."
      },
      "PartnerWorksheet": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "PlaySpace worksheet id. Use it to open the worksheet in an embedded viewer."
          },
          "title": {
            "type": "string",
            "description": "The worksheet's title as it appears in the clinician's library.",
            "example": "Feelings Thermometer"
          },
          "page_count": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Number of pages in the worksheet.",
            "example": 3
          },
          "shelf_ready": {
            "type": "boolean",
            "description": "Read-only and derived: true when this worksheet is ELIGIBLE to be placed on a playroom or toolkit shelf — that is, when an attach will accept this id. Eligibility is not placement: an eligible worksheet may be on no shelf at all, and a worksheet already on one stops being eligible if its publication state changes. Attaching an id whose `shelf_ready` is false is refused with a 422 carrying `reason: not_shelf_ready`, so read this before attaching rather than after.",
            "example": true
          },
          "thumbnail_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Short-lived signed URL for the cover image, or null when none exists. Expires — re-list rather than caching it."
          },
          "created_at": {
            "type": "string",
            "description": "ISO-8601 creation timestamp."
          },
          "updated_at": {
            "type": "string",
            "description": "ISO-8601 last-update timestamp."
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The worksheet's description, or null when it has none. Clinician-authored library text.",
            "example": "Scaling exercise used at the start of a session."
          },
          "category": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Free-text category the worksheet is filed under, or null when it has none.",
            "example": "Emotional regulation"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags on the worksheet. Empty when it has none — never null.",
            "example": [
              "anxiety",
              "scaling"
            ]
          },
          "deleted_at": {
            "description": "ISO-8601 timestamp at which the worksheet was deleted, or null when active. Returned only when `include_deleted=true`; otherwise omitted.",
            "example": null,
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "title",
          "page_count",
          "shelf_ready",
          "thumbnail_url",
          "created_at",
          "updated_at",
          "description",
          "category",
          "tags"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheet",
        "description": "One worksheet in the acting practitioner's library."
      },
      "PartnerWorksheetDetailResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerWorksheetDetail"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetDetailResponse",
        "description": "Top-level envelope for GET /v1/partner/worksheets/{id}."
      },
      "PartnerWorksheetPagesResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerWorksheetPage"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetPagesResponse",
        "description": "Top-level envelope for GET /v1/partner/worksheets/{id}/pages. Every page of the worksheet, in reading order — a worksheet is small enough that this is never paginated."
      },
      "PartnerWorksheetPageResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerWorksheetPage"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetPageResponse",
        "description": "Top-level envelope for GET /v1/partner/worksheets/{id}/pages/{pageId}."
      },
      "PartnerWorksheetShareResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PartnerWorksheetShare"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMetaWithoutPagination"
          }
        },
        "required": [
          "data",
          "meta"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetShareResponse",
        "description": "Top-level envelope for POST /v1/partner/worksheets/{id}/share."
      },
      "PartnerWorksheetShare": {
        "type": "object",
        "properties": {
          "share_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Handle for this share, or null if one could not be recorded. The email is sent either way."
          },
          "worksheet_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The worksheet that was sent."
          },
          "patient_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "The patient it was sent to."
          },
          "expires_at": {
            "type": "string",
            "description": "ISO-8601 instant after which the emailed link stops working.",
            "example": "2026-09-15T15:30:00.000Z"
          }
        },
        "required": [
          "share_id",
          "worksheet_id",
          "patient_id",
          "expires_at"
        ],
        "additionalProperties": false,
        "title": "PartnerWorksheetShare",
        "description": "Result of emailing a worksheet to a patient. Carries no address and no link — both are known only to the recipient."
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Paste an access token you have already minted. This is the way to exercise the API from a rendered console: the token exchange itself cannot run in a browser (see `oauth2`), so obtain the token from a terminal and bring it here.\n\nOrganization token — reads every clinic, practitioner, patient and appointment your organization owns:\n\n```\ncurl --request POST --url https://YOUR_TENANT.auth0.com/oauth/token \\\n  --header 'content-type: application/json' \\\n  --data '{\"client_id\":\"...\",\"client_secret\":\"...\",\n           \"audience\":\"https://playspace-ehr-api\",\n           \"grant_type\":\"client_credentials\"}'\n```\n\nDelegated token — additionally reaches the clinician-owned content surfaces (worksheets, storybooks, playrooms, toolkits, forms, game saves, embed tokens, game sessions), which refuse an organization token with 403. Add one field:\n\n```\n           \"practitioner_id\":\"<id from GET /v1/partner/practitioners>\"\n```\n\nDo not request scopes. Your grant is fixed on the credential at approval and travels inside the token; asking for one the credential lacks fails the exchange with `403 access_denied`."
      },
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth2 client-credentials access token issued by Auth0 for your partner organization. Exchange your client id and secret for a token, then send it as `Authorization: Bearer <token>`.\n\nYour granted scopes are fixed on the credential at approval and are carried inside the token — you never request them. The vocabulary is `practitioners:read` (read practitioners), `practitioners:write` (create and update practitioners), `practitioners:delete` (delete practitioners), `clinics:read` (read clinics), `clinics:write` (create and update clinics), `clinics:delete` (delete clinics), `patients:read` (read patients), `patients:write` (create and update patients), `patients:delete` (delete patients), `appointments:read` (read appointments), `appointments:write` (create and update appointments), `appointments:delete` (delete appointments).\n\nThis exchange yields an ORGANIZATION token, which reads every clinic, practitioner, patient and appointment your organization owns. The content surfaces (worksheets, storybooks, playrooms, toolkits, forms, game saves, embed tokens, game sessions) are owned by a clinician rather than by an organization and refuse an organization token with 403. For those, add `practitioner_id` to the token request to obtain a delegated token acting as that clinician.",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://dev-hcp1velit44csg3n.us.auth0.com/oauth/token",
            "refreshUrl": "https://dev-hcp1velit44csg3n.us.auth0.com/oauth/token",
            "scopes": {},
            "x-scalar-security-body": {
              "audience": "https://playspace-ehr-api"
            }
          }
        }
      }
    }
  }
}
