Show a client's blood test progress inside your coaching app: ordered, results back, report sent, next test due. Milestones only, never a result, and only for clients who said yes. A small REST API, signed webhooks, a sandbox and an OpenAPI file. Most teams are done in a day.
How it fits
Consent
Nothing is shared by default. At checkout through a partner's link the client sees one unticked box: "Let {coach} see when my bloods are ordered, collected and back." They can turn it on or off at any time in their HYLABS portal under Account. We log the wording and the time of every change.
When a client stops sharing, your API view drops them within fifteen minutes and you receive a sharing.revoked event. Your agreement with HYLABS requires you to delete what you hold about that client within seven days. Attribution alone never grants access: a coach whose link was used sees nothing until the client says yes.
What you get
| Field | Example | Meaning |
|---|---|---|
| client_ref | cl_3f9a... | Opaque id, stable per client per partner. Not reversible. |
| external_id | kah_88213 | Your own client id, when your link carried ?ext=. |
| order_ref | HY-TEST0001 | The HYLABS order id. |
| panel | Physique Standard | The panel ordered. |
| tier | results | report | Results only, or results with the doctor's written report and call. |
| status | ordered | form_expired | in_review | results_back | report_sent | call_booked | call_held | retest_due | refunded | Where the order is up to. One value, highest precedence wins. |
| ordered_at, form_expires_at | ISO 8601 | When the order was placed and when the request form lapses. |
| in_review_at, results_at, report_at | ISO 8601 or null | Each set when it happens. |
| call_booked_for | ISO 8601 or null | The doctor call, report tier only. |
| next_due_on | YYYY-MM-DD or null | Suggested next test, 90 days after results or report. |
| order_url | https://hylabs.com.au/checkout?... | Your link for the client's next order, with ext carried through. |
{
"client_ref": "cl_3f9a1c0b7d2e4f6a8b9c0d1e",
"external_id": "kah_88213",
"sharing": { "status": "on", "since": "2026-11-04T09:12:00+11:00" },
"updated_at": "2026-11-12T16:40:12+11:00",
"latest": {
"order_ref": "HY-TEST0002",
"panel": "Physique Standard",
"tier": "report",
"status": "report_sent",
"ordered_at": "2026-11-04T09:11:40+11:00",
"form_expires_at": "2026-12-04T09:11:40+11:00",
"in_review_at": "2026-11-10T08:02:00+11:00",
"results_at": "2026-11-12T16:40:12+11:00",
"report_at": "2026-11-12T16:40:12+11:00",
"call_booked_for": null,
"next_due_on": "2027-02-10",
"order_url": "https://hylabs.com.au/checkout?panel=standard&ref=SAMFIT&ext=kah_88213"
},
"orders": [ "..." ]
}What you never get
The status names are deliberately plain (results_back, not a clinical word). Nothing in this API is clinical information, and nothing in it may be presented to a client as advice.
Access
Base URL https://api.hylabs.com.au/v1. Every request carries Authorization: Bearer hk_live_.... Keys are issued by HYLABS per partner, shown once and stored hashed. A hk_test_ key answers every route from a fixed sandbox data set (two clients, nine orders, one in every status, twenty events) and still delivers real webhooks to your endpoint, so you can build the whole integration before a single real client shares anything. Ten requests a second; responses are never cached.
Errors are { "error": { "code": "...", "message": "..." } } with 400, 401, 403 (partner not active), 404 or 429.
Routes
| GET | /v1/me | Your partner record: code, links, service area, webhook. |
| GET | /v1/clients?updated_since=&limit=&cursor= | Every consented client with their latest order and history. |
| GET | /v1/clients/{client_ref} | One client. |
| GET | /v1/clients/by-external/{external_id} | One client by your own id. 404 when unknown or not shared. |
| GET | /v1/events?since=&limit= | The same events the webhook sends, for polling. |
| GET | /v1/webhook | Your webhook endpoint (the secret is shown once, at creation). |
| PUT | /v1/webhook | Register or replace the endpoint URL. Returns the signing secret this one time. |
| DELETE | /v1/webhook | Remove the endpoint. |
| POST | /v1/webhook/test | Send a ping event to your endpoint. |
| GET | /v1/webhook/deliveries?limit= | Recent delivery attempts and their outcomes. |
| GET | /v1/openapi.json | The OpenAPI 3.1 document. No auth. |
Lists page with cursor; updated_since keeps a poll cheap. The view behind the API is refreshed every fifteen minutes, which is instant for bloodwork.
openapi.json describes every route and schema.
Webhooks
Each event is a JSON body { id, type, created_at, data } posted to your URL with the headers HYLABS-Event, HYLABS-Delivery and HYLABS-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of `${t}.${rawBody}` with your signing secret (header names arrive in whatever case the transport gives them; read them case-insensitively). Any 2xx within ten seconds counts as delivered; otherwise we retry every fifteen minutes for 24 hours. Verify with a constant-time compare and reject a timestamp more than five minutes old.
Node
import crypto from 'node:crypto';
export function verify(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(',').map(kv => kv.split('=')),
);
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
const given = Buffer.from(parts.v1 || '', 'hex');
const want = Buffer.from(expected, 'hex');
return given.length === want.length && crypto.timingSafeEqual(given, want);
}Python
import hmac, hashlib, time
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
parts = dict(kv.split("=", 1) for kv in signature_header.split(","))
t = int(parts.get("t", "0") or 0)
if not t or abs(time.time() - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Terms
The partnership is mutual and there is no fee either way: your coaches and their clients get doctor-backed private pathology with no referral, results in days and a written report, and your app gets a bloods status it did not have to build. While your integration is active you may describe it as "Bloods by HYLABS" or "Backed by HYLABS". You may not present HYLABS results, reports or this API as programming, treatment or an endorsement of any substance, and you may not make outcome claims using our name. Data stays in Australia on our side; the agreement covers purpose limitation, deletion on revoke, and breach notice. HYLABS collects at 1,000+ centres across NSW, VIC, QLD and ACT.
To get a sandbox key or talk through an integration, use the contact form and mention your platform.