Skip to main content

Getting connected

Where do I get an API key, and why doesn't my old one work?

Developers → API Keys → API key in PracticeHub (a team member with the developer permission). API keys are Bearer tokens that look like phk_…. (An older-style key from an earlier PracticeHub API will not work here.) See Authentication.
Every clinic account has its own host: https://<your-clinic>.<region>.practicehub.io/v3/api — exactly the host the practice signs in on; the region host is the one in the practice’s login URL — neptune (London), mercury (Frankfurt), vulcan (Ohio), jupiter (Singapore) or apollo (Sydney). The docs use {clinic}.{region} / your-clinic.your-region because there is no single URL — which is also why the “Try it” button is off on the reference pages: copy the request into your terminal with your host instead.
GET /me — returns the integration, its scopes and the account (id, name, timezone, currency). Make it your first call.
Yes — a developer account: a fully-fledged PracticeHub account of your own, on its own subdomain with its own isolated database, provided for building and testing integrations. It behaves exactly like production because it is a real account — same rules, same validation, same status codes — with no real patient data to worry about. Email help@practicehub.io to request one. See Testing your integration. Every request is visible on Developers → API Logs, so test traffic is easy to review.
Not during the limited rollout: only Claude and ChatGPT can register as connectors — any other client is refused at registration — and the server accepts OAuth approved by a team member, never an API key. Build your own agent or automation on the REST API with an API key instead: it gets reads and writes today, with the same rules and the same GET /schema contract.
Yes — during the limited rollout the server registers no write tools, so a connected assistant can look anything up but cannot book, cancel, take payments or edit records. No date is published for widening the rollout; the changelog announces it when it happens. Writes are available now on the REST API.
Yes — this site is an MCP server at https://build.practicehub.io/mcp, with a skill file at /skill.md and a page index at /llms.txt. Snippets for Claude, Claude Code, ChatGPT, Cursor, VS Code and Windsurf are on Docs MCP. That server reads documentation only; it cannot see a clinic’s data.

Errors

No Authorization: Bearer header, a typo in the key, an expired or revoked key — or the right key on the wrong clinic host (keys are per account). Check the host first.
The key was created Read only and you sent a write. Create a Read & write key for this integration (don’t widen a key that other systems already use).
Only for OAuth connectors (Claude, ChatGPT…): the team member’s PracticeHub role doesn’t allow that read/write/delete, or no longer has Connect AI apps. The message names the permission; an account owner changes it under Settings → Security → Roles. API keys are never affected by roles.
Wrong clinic host; the record is soft-deleted or voided (it no longer comes back); or — for connectors — it is outside the person’s own-patients / own-calendar scope. There is no “hidden but present” state: a 404 means you cannot have it.
That resource doesn’t support that operation: some are read-only (practitioners, modalities, resources, care_plans, numbers, addresses, …), some can’t be deleted (payment_methods, referral_sources, appointment_types, locations, billable_items). GET /schema/{resource} lists what is allowed. See Resources.
All three mean the practice’s own rules refused it, same as a staff member would be refused in the app. group_full: the group session has no places left — re-query availability. (A plain booking on a taken time is not refused — POST /appointments books what you send, like a staff click; check availability first.) appointment_rule: the appointment’s state forbids the change (already checked out, cancelled, missed) — the message says which. billing_rule: an invoice/payment rule (already void, over-allocation, last line item…). Show the message; don’t retry.
Read errors: it names the attribute. Common ones: starts_at not YYYY-MM-DDTHH:MM:SS; an unknown attribute or filter name (they are refused, not ignored); a numbers.*.number that isn’t a real phone number for the iso2 given, or a number without a type; amount with more than 2 decimals; a metadata key over 40 chars.
120 requests per minute per account, per surface — every API key on the account shares one REST bucket; AI connectors draw from a separate MCP bucket. Wait Retry-After seconds. If you hit it in normal operation you are polling something — use filters, page_size=100, cursors and webhooks. See Best practices.
Developers → API Logs in PracticeHub: every request by key, with method, path, status, latency and X-Request-Id (no bodies). Quote the X-Request-Id when you contact support.

Data & behaviour

Appointment starts_at/ends_at, invoice_date, payment_date and availability slots are clinic-local with no offset; created_at/updated_at are UTC. Don’t convert the former; use /me for the timezone. See Best practices → Time.
So no client rounds them as floats. "45.00" in, "45.00" out — parse into a decimal type.
A timeout after PracticeHub wrote the record. Always send an Idempotency-Key on creates: a retry with the same key returns the original response instead of booking again. See Writing.
numbers is reconciled by id: send the stored numbers with their id (you get them on every read) to keep/update them, add new ones without an id, and leave out the ones to remove. A list with no ids at all is treated as “replace everything”. Same model as line_items on invoices.
Voiding soft-deletes a payment (and the allocations an invoice void releases): they stop being returned by list and get endpoints, and appear in the deleted_entities tombstone feed. (Billing webhook events are not in the current rollout — patient and appointment events first; see Webhooks.) Voided invoices stay visible with state: "void".
Clinical notes: yes — read and create them through /clinical_notes (see Clinical notes). SMS and card payments: no — messaging is the practice’s, and a payment a provider collects (card, Direct Debit) cannot be recorded through the API because no money is collected (card_payment_not_allowed). See Resources for what is and isn’t available.
visibility=online (the default) only offers what the practice has enabled for online booking — location, appointment type and practitioner. visibility=all shows the staff view. availability_not_configured means there is no rota or service for that location + type at all. See Booking.
No — webhooks are live (in a limited rollout, patient and appointment events first), and payloads carry the event type and record id only (no personal or clinical data leaves via webhooks). Fetch the record. Events aren’t ordered; the record you fetch is always current.
It timed out or returned non-2xx repeatedly. Acknowledge within a couple of seconds and do the work afterwards; then re-enable and replay missed messages from Developers → Webhooks.
Yes — put them in metadata and filter ?metadata[your_key]=eq:value (or =null for “not yet synced”). See Metadata.
Not answered here? Email help@practicehub.io with the clinic subdomain, the endpoint, and the X-Request-Id from the response.