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

# Changelog

> Every change to the API contract, newest first

The API is **additive within a version**: new resources, attributes, event types and optional inputs may appear at any time; nothing is renamed, removed or made required. Anything that would break a client goes to a new version with a published deprecation window. Each entry below is dated and marks additive changes with **Added** and behaviour clarifications with **Changed**.

<Update label="2026-08-17" description="Postman, API Logs report, base path">
  **Changed**

  * Updates are `PATCH` only (partial, merge-style). `PUT` is not supported and returns `405` — it was previously accepted as an alias of `PATCH`, which misread as full replacement.
  * **First-release resource set.** Live: `patients`, `appointments`, `availability`, `invoices`, `line_items`, `payments`, `payment_allocations`, `files`, `patient_logs`, `patient_log_tags`, `payment_methods`, `referral_sources` (create/update), and read-only `appointment_types`, `modalities`, `practitioners`, `locations`, `resources`, `practitioner_availabilities`, `numbers`, `addresses`, `deleted_entities`. Switched off for now: `notifications`, `banners`, `patient_payment_methods`, `discount_codes`, `exercises`, `self_service_portals`; `appointment_types` and `modalities` are read-only.

  **Added**

  * `POST /line_items` accepts `lines: [...]` to add several lines to an invoice in one atomic call (returns the created lines).
  * `resources` (read-only): calendar resources — rooms, tables, benches — that `resource_id` on appointments and availability slots refers to.
  * [Postman collection](/postman) and environments generated from the OpenAPI document — every endpoint, bearer auth from `{{api_key}}`, ids chained between requests, safe-by-default guards for updates and deletes.
  * **Developers → API Logs is now a report**: window presets or a custom range in the clinic timezone; filters by credential (integration or individual key, connector user), method, resource, outcome, status code, error code, latency, path and request id; usage summary (requests, success rate, 4xx / 429 / 5xx, p50 / p95 latency); requests-over-time chart; breakdowns by credential and endpoint; top error codes; per-request detail; CSV export. Each log row now also records the calling key, resource and record id, the API's error `code` and message, response size, idempotent-replay flag and user agent — still no bodies or parameter values.
  * Records created through the API show the integration name (for example *"API: Booking widget"*) as their creator in PracticeHub's ledger and patient file, instead of a generic system label.

  **Changed**

  * **Base path is `/v3/api`** (`https://{clinic}.{region}.practicehub.io/v3/api/...`); the MCP server is at `/v3/api/mcp`. Pre-release material that showed `/v3/api/v1` is superseded.
  * API keys are issued as the bare secret (`phk_…`); the Developers → API Keys page shows each key as `phk_••••••••••••` plus its last four characters. Keys issued earlier in the `{id}|phk_…` form keep working.
</Update>

<Update label="2026-08-16" description="Availability">
  **Added**

  * **Typed metadata**: values may be strings, numbers or booleans and read back typed; numeric operands to `gt`/`gte`/`lt`/`lte`/`between` compare numerically. New `contains` operator (case-insensitive substring, no escaping) on attributes and metadata alike.
  * `patients` reads now embed `numbers[]` and `address` in the same shape the write accepts (loaded per page, not per row) — a patient round-trips in one call.
  * **Cursor pagination** on every list: `?cursor=` to start, follow `links.next`; constant cost per page, stable under concurrent writes; `meta: {per_page, has_more}`. `page`/`page_size` unchanged. See [Querying](/guides/querying#cursor-pagination).
  * `GET /availability` — free, bookable slots for a location and appointment type over up to 31 days, in the patient (`visibility=online`) or front-desk (`visibility=all`) view; `meta.unavailable_dates` for days with nothing free. See [Booking](/guides/booking).
  * MCP `check_availability` tool on the same engine.
  * `allow_clash` on `POST /appointments` for deliberate overbooking.
  * `DELETE /invoices/{id}?void_payments=true` — also void manual payments the void leaves with nothing to pay (documented; MCP `write_entity` takes it in `attributes`).
  * `PATCH /invoices/{id}` reconciles `line_items` by `id` — lines keep their ids across edits; without `line_items` it changes the header only.
  * `invoices` now read back `number`, `state`, `type`, `invoice_date`, `note`, `subtotal`, `balance`, `patient_balance`, `third_party_balance`; `payments` read `payment_date` and `status`; `appointment_types` read `online_booking` and `active` (all filterable; sortable where it makes sense).
  * [Resources & roadmap](/resources) and [Security & data handling](/security) pages.

  **Added**

  * MCP `check_availability` accepts `rescheduled_appointment_id`, matching `GET /availability`, so an assistant can find a new time for an existing appointment without its current slot counting as busy.

  **Changed**

  * Connected AI apps (OAuth connectors) now act with the team member's PracticeHub role: reads, writes and deletes need the same permissions as in the app, and own-patients / own-calendar roles are ringfenced. Refusals are `403 permission_denied`. API keys are unchanged. See [Connectors](/mcp/connectors#what-a-connected-team-member-can-do).

  **Changed**

  * `PATCH /patients/{id}` reconciles `numbers` by `id` (as `line_items` already did): send each stored number's `id` to update it in place, omit an id to add, leave a stored number out to remove it. Ids come back on every patient read. A list with no ids at all keeps the previous replace-everything behaviour.

  **Fixed**

  * `patients` `numbers[].type` is now **required** (`mobile` / `home` / `work` / `other`); a number without one was silently stored as `mobile`. Every read already returns the type.
  * `PATCH /invoices/{id}` with `note: null` clears the note (it was previously ignored); an absent `note` still leaves it alone.
  * `PATCH /files/{id}` may change `type` only between `generic`, `custom_content` and `diagnostic_image` — `patient_profile` (image-only) and `patient_log` (linked to a log entry) are fixed at upload.
  * Writes to reference resources (`appointment_types`, `locations`, `practitioners`, `referral_sources`, …) are validated by column type — booleans, integers, amounts, dates, enums, string lengths — so a wrong-typed value is a `422` naming the attribute rather than a server error. The OpenAPI schemas carry the types.
  * `patients.sex` now reads as `male` / `female` / `other` (or `null` when unknown) and is filtered by those words — the same values it accepts on write. It previously read back the stored code.
  * `GET /availability` answers `422 availability_not_configured` (with the engine's reason) for a location or appointment type that has no calendar/type settings, instead of an error.
  * `line_items` is writable: `POST` (add a line to an invoice), `PATCH`, `DELETE` — one line at a time, re-totalling the invoice; `PATCH /invoices/{id}` with `line_items[]` reconciles the whole set. `patient_payment_methods` stays read-only.

  **Changed**

  * `practitioners`, `locations`, `practitioner_availabilities`, `numbers` and `addresses` are **read-only** for now (`405 unsupported_operation` on write). The app creates practitioners (user + roles + licence), locations (address + calendar settings) and availability (series, broadcasts) through flows a plain column write would skip; phone numbers and addresses are normalised through `patients`. Writes return when they are routed through those services. Reads are unchanged.
  * `POST /appointments` now refuses a time that is not free in the practitioner's diary with `409 slot_unavailable` (previously the appointment was written regardless, as a staff click is). Group bookings (`group_master_id`) are unaffected — they already answered `409 group_full`.
  * An invoice can no longer be created, or edited down to, zero line items (`422` on create, `409 billing_rule` on the last `DELETE /line_items/{id}`).
</Update>

<Update label="2026-08-15" description="Initial release">
  **Added**

  * REST API at `/v3/api` — 25 resources with uniform list / get / create / update / delete, the legacy filter syntax, sorting and pagination.
  * `GET /schema` and `GET /schema/{resource}` — attributes, filterable/sortable flags, write operations and per-operation input rules.
  * `GET /me` — the credential, its scopes, and the account (name, timezone, currency).
  * API keys (`phk_…`), **read & write** or **read only**, optional expiry, rotation with a 24 h grace period.
  * `Idempotency-Key` on `POST` — 24 h replay with `Idempotent-Replayed: true`.
  * `metadata` on every record — your own key/values, filterable with `metadata[key]=op:value`.
  * Machine-readable error `code` on every error alongside `message` (and `errors` for input problems).
  * MCP server at `/v3/api/mcp` — generic `list_entity_types` / `query_entities` / `get_entity` / `write_entity` tools plus curated patient and appointment tools; API keys or OAuth 2.1 connectors (per-role opt-in, in-app revoke).
  * Webhooks — **coming soon**, not yet switched on for any account; the contract is documented: `{resource}.created/updated/deleted` for every observed resource plus `appointments.rescheduled/cancelled/missed/arrived/reinstated`; thin signed payloads; self-serve portal under **Developers → Webhooks**.
  * `X-Request-Id` on every response, mirrored on **Developers → API Logs** (retained 90 days).

  **Changed** (relative to the legacy `/api`)

  * Filters on unknown attributes are `422` (legacy did not validate them); a value without an operator prefix is `eq`.
  * Lists always have a stable order (record id is the final tiebreaker).
  * Writes to patients, appointments, payments, invoices, allocations and files run through the same services as the PracticeHub app; deletes of payments/invoices **void** them.
  * See [Migrating from the legacy API](/guides/migrating-from-legacy) for the full envelope and behaviour comparison.
</Update>
