Get started
Requests, responses & errors
The few rules every endpoint follows. Learn them once and the whole API feels the same.
Base URL#
Every endpoint starts with this address (it is the same site as HealthFix itself):
https://your-healthfix-site/api/v1So “GET /patients” in these docs means a GET request to https://your-healthfix-site/api/v1/patients. Always use https.
Headers to send#
| Header | When | Value |
|---|---|---|
Authorization | Always | Bearer YOUR_API_KEY |
Content-Type | When you send a body (POST, PUT, PATCH) | application/json |
Methods#
| Method | Means | Example |
|---|---|---|
GET | Read something | GET /appointments |
POST | Create something new | POST /patients |
PUT | Replace something with a full new version | PUT /appointments/{id} |
PATCH | Change one part | PATCH /appointments/{id}/status |
In paths, {id} is a placeholder for the record's ID, a 36-character code like 0b9f6f7e-3c1a-4f7e-9b1e-5f2d8a7c1e42 that you get from earlier responses.
Successful responses#
Successful answers use status 200 (or 201 when something was created) and always put the result in data:
{ "data": { "id": "…", "full_name": "Ravi Kumar", "…": "…" } }Fields that have no value are null. Unknown fields you send are rejected, which catches typos early.
Dates, times and money#
- Dates are
YYYY-MM-DD, e.g.2026-10-01. - Times are ISO 8601 with a time zone, e.g.
2026-10-01T10:30:00+05:30(10:30 in India) or2026-10-01T05:00:00Z(UTC). HealthFix answers in UTC (theZ). - The clinic's own time zone and currency are in
GET /clinic/preferences. - Amounts are plain numbers in the clinic's currency, e.g.
500means ₹500.
Long lists (pagination)#
Lists come in pages. Use limit for the page size; when there are more, meta tells you how to get the next page:
{
"data": [ … 25 patients … ],
"meta": { "has_more": true, "next_cursor": "MjAyNi0wMy0wMlQxMDoxNTowMFp8…" }
}Ask again with ?cursor= set to next_cursor, and repeat until has_more is false:
let cursor = "";
const all = [];
do {
const res = await fetch(`https://your-healthfix-site/api/v1/patients?limit=100&cursor=${cursor}`, {
headers: { Authorization: "Bearer " + process.env.HEALTHFIX_API_KEY },
});
const body = await res.json();
all.push(...body.data);
cursor = body.meta?.has_more ? body.meta.next_cursor : "";
} while (cursor);Search results (?q=) are different: they return the best matches in one go, without pages.
Errors#
When something goes wrong you get a status of 400 or higher and an error object with a code for your program and a message for people:
{
"error": {
"code": "validation_failed",
"message": "One or more fields are invalid",
"fields": { "gender": "is required" },
"request_id": "api-7f3c/Qx9bT2kLmN-000042"
}
}| Status | Code | What it means and what to do |
|---|---|---|
| 400 | bad_request | Something in the request is malformed, like an invalid date. Read the message. |
| 401 | unauthorized / api_key_invalid | The key is missing, mistyped, expired or revoked. Check the Authorization header. |
| 403 | api_key_scope | The key works but lacks the scope for this endpoint. Create a key with that scope. |
| 403 | api_key_forbidden | Keys can't use this endpoint at all (for example deleting). Use the app. |
| 404 | not_found | No record with that ID in your clinic. |
| 409 | conflict | Clashes with existing data, e.g. the doctor already has an appointment at that time. |
| 422 | validation_failed | Some fields are wrong. fields says which, in plain English. |
| 429 | rate_limited | Too many requests. Wait a second and retry. |
| 402 / 403 | subscription codes | The clinic's plan has lapsed or is read-only. Reading still works; writing is paused. |
| 500 | internal_error | Our fault. Retry later; if it keeps happening, send us the request_id. |
error.request_id. With it, support can find exactly what happened to that call.Rate limits and retries#
Up to 5 requests a second per key, bursts to 60. If you get 429, or a 5xx error, wait and retry, doubling the wait each time (1 s, 2 s, 4 s…). Don't automatically retry 4xx errors other than 429; they won't fix themselves.
async function healthfix(path, options = {}, attempt = 0) {
const res = await fetch("https://your-healthfix-site/api/v1" + path, {
...options,
headers: {
Authorization: "Bearer " + process.env.HEALTHFIX_API_KEY,
"Content-Type": "application/json",
...options.headers,
},
});
if ((res.status === 429 || res.status >= 500) && attempt < 4) {
await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
return healthfix(path, options, attempt + 1);
}
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
return body.data;
}
// Usage
const doctors = await healthfix("/users?role=doctor");