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

# Migrating from the legacy API

> Moving an integration from the legacy /api to the PracticeHub API at /v3/api

The legacy REST API at `https://{clinic}.{region}.practicehub.io/api` is being retired in favour of the new PracticeHub API — see [Why the new API](/why-the-new-api#why-retire-the-legacy-api-rather-than-extend-it) for the reasoning. Resource names, attribute names and the filter syntax are unchanged, so for most integrations the move is: create a v1 key, change the base URL and auth header, and update how you read responses.

<Note>
  Legacy responses now carry `Deprecation`, `Sunset` and `Link` headers with the retirement date and a link to this guide. Until that date both APIs run side by side on every clinic domain, so you can migrate one call at a time.
</Note>

## 1. Credentials

Legacy keys do not work on the new API. Create an **API key** under **Developers → API Keys** and send it as a Bearer token; the `x-app-details` header is no longer needed (the key itself identifies your integration).

|          | Legacy                                              | New API                                           |
| -------- | --------------------------------------------------- | ------------------------------------------------- |
| Base URL | `https://{clinic}.{region}.practicehub.io/api`      | `https://{clinic}.{region}.practicehub.io/v3/api` |
| Auth     | `x-practicehub-key: …` + `x-app-details: app=email` | `Authorization: Bearer {key}`                     |

## 2. Requests

|               | Legacy                                                                 | New API                                                                                                                                                                                                                                                                         |
| ------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| List / filter | `GET /{resource}?field=op:value`                                       | Same                                                                                                                                                                                                                                                                            |
| Get one       | `GET /{resource}/{id}`                                                 | Same                                                                                                                                                                                                                                                                            |
| Create        | `POST /{resource}`                                                     | Same                                                                                                                                                                                                                                                                            |
| Update        | `POST /{resource}/{id}`                                                | `PATCH /{resource}/{id}`                                                                                                                                                                                                                                                        |
| Delete        | `DELETE /{resource}/{id}`                                              | Same                                                                                                                                                                                                                                                                            |
| Page size     | `page_size`                                                            | Same (max 100)                                                                                                                                                                                                                                                                  |
| Free slots    | `GET /timeslot_availability?location_id&appointment_type_id&start&end` | `GET /availability?location_id&appointment_type_id&from&to` — see [Booking](/guides/booking); `practitioner_id`, `max_appointments_per_day` → `max_per_day` and `rescheduled_appointment_id` carry over, `include_non_online_bookable_practitioners=1` becomes `visibility=all` |

Filter operators (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `like`, `in`, `not-in`, `between`, `null`, `not-null`) and `sort` are identical. Two tightenings: a value with no operator prefix is now treated as `eq` (legacy ignored it), and filtering on an attribute the resource does not expose is a `422` (legacy did not validate it).

## 3. Responses

This is the one mechanical change every client makes:

|         | Legacy                                                                        | New API                                                                                                                   |
| ------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| List    | `{ "total_entries", "data": [...], "links": { "previous", "self", "next" } }` | `{ "data": [...], "links": { "first", "prev", "next" }, "meta": { "total", "per_page", "current_page", "last_page" } }`   |
| Get one | attributes at the top level                                                   | wrapped in `data`                                                                                                         |
| Create  | `201 { "patientID": 123 }`                                                    | `201` with the full record under `data`                                                                                   |
| Update  | `{ "updated": true }`                                                         | the updated record under `data`                                                                                           |
| Delete  | `200`                                                                         | `204`, empty body                                                                                                         |
| Errors  | four different shapes, some with an empty body                                | always `{ "message", "errors"? }` — see [Errors](/guides/errors)                                                          |
| Types   | everything a string (`"12"`)                                                  | ids and other integer attributes are JSON numbers; money amounts are decimal strings (`"45.00"`); empty values are `null` |

Every record additionally has an `id` and a [`metadata`](/guides/metadata) object.

## 4. Behaviour differences

New-API writes to `patients`, `appointments`, `payments`, `invoices`, `payment_allocations` and `files` go through the same services as the PracticeHub app, where the legacy API used a generic CRUD layer. In practice:

* Bookings are validated as in the calendar (active patient / location / practitioner, resource at that location); status changes are real transitions with side effects (cancel, mark missed, arrive, reinstate) rather than a column update.
* Payments must use a real, active payment method; deleting a payment or invoice **voids** it rather than removing the row.
* Confirmation and reminder messaging is triggered by API bookings exactly as by staff bookings.
* `patient_payment_methods` is not in the first release (stored cards are managed through PracticeHub). `line_items` is writable (`POST` with `invoice_id` and one line or `lines[]`, `PATCH`, `DELETE`); `PATCH /invoices/{id}` with `line_items[]` reconciles the whole set by id.
* Reference data other records point at — `payment_methods`, `referral_sources` — can be created and updated but not deleted through the new API (`405`); deactivate in PracticeHub instead. `appointment_types`, `modalities`, `practitioners`, `locations` and `practitioner_availabilities` are read-only for now (the app creates them through flows with side effects a plain column write would not run); `numbers` and `addresses` are written through `patients`.
* `id`, `created` and `updated` are server-managed on every resource; the legacy API let clients supply them.
* These resources are not on the new API yet: `clinical_notes`, `care_plans`, `packages`, `patient_packages`, `custom_forms`, `custom_form_responses` — see [Resources & roadmap](/resources) for the status of each. If your integration relies on one of them, or on a legacy endpoint that is not a resource, tell us at [support@practicehub.io](mailto:support@practicehub.io) — that feedback shapes what is added next.

## 5. Checklist

<Steps>
  <Step title="Create an API key">Developers → API Keys → API key. Store it server-side.</Step>
  <Step title="Change base URL and auth header">`/api` → `/v3/api`; two headers → one Bearer token.</Step>
  <Step title="Update response parsing">Read `data` / `meta` / `links`; expect full records back from create and update; expect `204` from delete.</Step>
  <Step title="Switch updates to PATCH">`POST /{resource}/{id}` no longer updates.</Step>
  <Step title="Handle errors by status">`422` with `errors` for input, `409` for business rules, `405` for unsupported operations.</Step>
  <Step title="Test against a real account">Compare a day's traffic side by side; the **Developers → API Logs** page shows every request with its status.</Step>
</Steps>
