code, find the request in Developers → API Logs, reproduce it with curl -i and read the headers, and check it against the gotchas below before you conclude something is broken.
1. Read the code
Every error is { message, code } (plus errors for a 422). message is written for a human and can be reworded without notice — branch your code, and your own eyeballing, on code. The full list of codes, grouped by status, is the error code catalogue; it’s generated from what the API actually emits, so if you see a code that isn’t there, it’s worth a support ticket on its own.
group_full is a 409 — a business rule, not a bug. Errors has the full retry taxonomy: 422 means fix the input, 409 (other than an idempotency replay) means the record’s state has to change first, 429/5xx are safe to retry with backoff.
2. Find it in API Logs
Developers → API Logs is a report over every call your account’s keys and connectors have made — a summary (success rate, error breakdown, p50/p95 latency), a timeline chart (hourly buckets up to 48 hours of range, daily beyond that, stacked by success /4xx / 429 / 5xx), breakdowns by credential and by endpoint, a top error-codes table, and the log itself: one row per request with its credential, method + path, status/error code, duration, and Request ID.
- Narrow before you scroll. Filter by time range (
1h/24h/7d/30d/90d, or a custom clinic-local date range), method, resource (entity), status class or exact status,error_code, a minimum duration, or a path substring. Rows are pruned after 90 days — a custom range further back than that is clamped, not silently empty. - Have a Request ID already? Paste it (or just the start of it) into the Request ID filter — it matches as a prefix, so a truncated id copied out of a client log still finds the row.
- Query parameter values aren’t logged, only their names. A filter’s
?patient_id=eq:340shows up asquery_keys: patient_id— the log tells you that you filtered onpatient_id, not what value you sent, because filter values can carry patient data. Cross-check the value against what your integration actually sent. - A
422’s row already tells you the field. The loggederror_messagefor a validation failure isn’t just “Validation failed.” — the first entry from the response’serrorsobject (field name + rule text) is appended, so the row is self-explanatory without opening the detail sheet. - Anonymous failures only show up if a credential was presented. A request with no bearer token at all that gets a
401/429isn’t logged (nothing to attribute it to); one with a bad or expired token is. Idempotent-Replayedis a column, not a guess. If a row is marked replayed, that request never re-ran your write — it’s a repeat of a key you (or a retrying HTTP client) already used.
X-Request-Id header (see below).
3. Reproduce with curl -i and read the headers
Run the failing call again with -i so you see the response headers, not just the body:
X-Request-Idis the one that matters for support and for API Logs — it’s what’s stored against the row, what the Request ID filter searches, and what the detail sheet tells you to quote. It’s generated fresh per request; you can’t set your own.X-Trace-Idis a second, separate id on every response (API and non-API alike) — it’s echoed back if you send your ownX-Trace-Idrequest header, or generated if you don’t. It is not stored in the API request log and doesn’t appear in API Logs, so it isn’t what you search the report with. It exists for PracticeHub engineering to correlate a request against internal application logs; mention it if support asks for it specifically, otherwise lead withX-Request-Id.X-RateLimit-Limit/X-RateLimit-Remainingare on every response, success or failure — worth glancing at even when the call worked, if you’re chasing an intermittent429.- On a
POSTyou retried with the sameIdempotency-Key, look forIdempotent-Replayed: true— it means you got the original response back, not a re-run of the write. If you expected a second record and got this instead, that’s the explanation, not a bug. - On a
429, readRetry-After— the number of seconds until the bucket has room. It’s per account, per surface (120/minute for REST, shared by every API key on the account — AI connectors have their own separate MCP bucket); a script and a manual test running at the same time spend the same allowance, so each exhausts it faster than either alone.
4. Common gotchas
Check these before assuming the API is wrong — each one has bitten a real integration and produces an error that looks like something else.When you contact support
Have these ready — all but the last come straight out ofcurl -i and the API Logs detail sheet:
- The
X-Request-Idfrom the response (or from the log row’s detail sheet) — this is what support pulls the request by. - The timestamp, with its timezone — clinic-local (as shown in API Logs) or UTC; either is fine as long as you say which.
- The endpoint — method and path (
POST /v3/api/appointments, not just “the appointments endpoint”). - The
codefrom the response body, not just the status —409 group_fulland409 appointment_ruleneed different fixes, and only the code tells them apart. - Whether an
Idempotency-Keywas involved, and whether you reused one — a replayed request behaving “wrong” is usually the original request’s outcome, not a new bug, and support needs to know you reused a key to rule that in or out.