Bloodwork Tracker API Documentation

External API reference reviewed against version 55 on 2026-09-08. Examples use synthetic data; abbreviated responses omit additional schema fields.

Open app

Base URL: https://health-tracker.project4.net/api

Authentication

Send one of these headers with external API requests:

X-API-Key: bwt_...
Authorization: Bearer bwt_...

Administrators create keys under API. Full keys are shown once only. Keys permit read, create, update and delete operations: they are not read-only and have no per-key read-only scope. Keep them out of public client code.

Patient Scope

Every clinical endpoint is restricted to the issuing account's personal patient and an active owner membership. Administrator status does not bypass this boundary. Client-supplied patient IDs do not select another patient. Sharing and patient switching are not enabled.

Bootstrap includes patient: {"id":1,"displayName":"Personal record","selfUserId":1}. IDs may be numeric strings. Revoked patient access returns 403.

Response Codes

Success Codes

Routed JSON data responses include X-BWT-Code. Objects also include a top-level code; arrays keep their array body. OPTIONS returns empty 204, and the separate public /api/build.php metadata endpoint is outside this contract.

BWT_SUCCESS
BWT_CREATED
BWT_OK

Error Body

{
  "code": "BWT_API_KEY_INVALID",
  "message": "Invalid or missing API key"
}
ERRORError Codes
BWT_INVALID_JSON      Request body could not be parsed as JSON
BWT_VALIDATION_FAILED Required fields or values are missing or invalid
BWT_INVALID_DATE      Date is not valid YYYY-MM-DD
BWT_INVALID_NUMBER    Number field is not numeric
BWT_INVALID_OTP       One-time login code is invalid
BWT_API_KEY_INVALID   API key is missing, inactive, revoked, or incorrect
BWT_UNAUTHORIZED      Browser session is missing or invalid
BWT_FORBIDDEN         Authenticated user cannot perform the action
BWT_NOT_FOUND         Route or generic resource not found
BWT_TEST_NOT_FOUND    Blood test not found for this user/key
BWT_RESULT_NOT_FOUND  Result not found for this user/key
BWT_CONFLICT          Request conflicts with current state
BWT_RATE_LIMITED      Too many requests
BWT_SERVER_ERROR      Unexpected server error

Schemas

SI is the default display. Results retain the original value and add measurement with original laboratory units/reference bounds, SI equivalents, conversion provenance and normalization status. Read measurement.original.unit when interpreting a result. Legacy snapshots are labelled legacy_assay_inferred. Unavailable conversions remain null.

Count units display as ×10⁹/L and ×10¹²/L. Assay writes accept ASCII and superscript variants, canonicalized to x10^9/L or x10^12/L with existing unit IDs and unchanged numeric values. Original reported labels and JSON exports are not prettified. Browser bootstrap supplies unitCatalog entries (id, unit) for input suggestions; no new external endpoint is added.

Result writes accept optional reported: {"unit":"mg/dL","low":70,"high":100}. Enter the value in that reported unit. Omission on an edit preserves stored metadata; omission on creation uses assay defaults. Result displays.native/si/conventional contain available display measurements. Preferences: si (default), conventional, both (SI + US), native.

measurement.status is normalized or needs_review; needs_review has null SI/conventional displays. Original origin is reported, assay_defaults or legacy_assay_inferred. Conversion versions identify frozen conversion rules, not app releases. Numeric database fields may be JSON numbers or numeric strings.

BloodTest

{
  "id": 123,
  "testName": "Haemoglobin",
  "groupName": "GENERAL HAEMATOLOGY",
  "unitOfMeasure": "g/L",
  "unitId": "g_l",
  "analyteId": "haemoglobin",
  "normalizedUnit": "g/L",
  "analyteName": "Haemoglobin",
  "refRangeLow": 135,
  "refRangeHigh": 180,
  "targetZoneEnabled": true,
  "targetZoneLow": 145,
  "targetZoneHigh": 165,
  "targetZoneUnit": "g/L",
  "archivedAt": null,
  "archivedReason": "",
  "conversions": [
    {
      "unitId": "g_dl",
      "unit": "g/dL",
      "multiplyBy": 0.1,
      "isApproximate": false,
      "source": "Derived metric prefix conversion",
      "notes": ""
    }
  ],
  "createdAt": "2026-05-06 10:00:00+00"
}

Normalized unit fields are included when a marker matches the built-in analyte catalog. unitOfMeasure remains the original/native unit text for backward compatibility. Target zones are visualization-only optimal ranges and do not affect low, high, or in-range status. Archived assays include archivedAt and archivedReason.

Result (Expanded Example)

{
  "id": 456,
  "testId": 123,
  "testDate": "2026-05-06",
  "value": 151,
  "measurement": {
    "original": {"value":151,"unit":"g/L","unitId":"g_l","analyteId":"haemoglobin","low":135,"high":180,"origin":"reported"},
    "si": {"value":151,"unit":"g/L","unitId":"g_l","low":135,"high":180},
    "conversion": {"factor":1,"version":"identity-v1","source":"Same unit"},
    "status":"normalized"
  },
  "displays": {
    "native": {"value":151,"unit":"g/L","unitId":"g_l","analyteId":"haemoglobin","low":135,"high":180,"origin":"reported"},
    "si": {"value":151,"unit":"g/L","unitId":"g_l","low":135,"high":180},
    "conventional": {"value":15.1,"unit":"g/dL","unitId":"g_dl","low":13.5,"high":18}
  },
  "createdAt": "2026-05-06 10:00:00+00"
}

External Endpoints

Write And Archive Semantics

Common response statuses; all endpoints may also return 401/403 or an unexpected 500.
OperationSuccessOther common failures
GET bootstrap/tests/results lists200 BWT_SUCCESS (header for arrays)400 invalid status
GET tests/{id}, results/{id}200 BWT_SUCCESS404
POST tests201 BWT_CREATED400
PUT/PATCH tests/{id}200 BWT_SUCCESS400, 404
POST results201 BWT_CREATED, including duplicate-date replacement400, 404
PUT/PATCH results/{id}200 BWT_SUCCESS400, 404
DELETE tests/{id}, results/{id}200 BWT_OK404
GET/PUT clinical-notes200 BWT_SUCCESS400 malformed JSON on PUT

These are common outcomes, not exhaustive validation guarantees. Handle all non-2xx responses. Supported 409/422/429 error mappings do not mean every endpoint enforces those checks. Shell examples below use Bash syntax; Windows users should adapt quoting or use Git Bash.

GET/external/bootstrap

Returns patient context, tests, results and clinical notes. status defaults to active; use all for archived history too.

Example request
curl "https://health-tracker.project4.net/api/external/bootstrap?status=all" \
  -H "Authorization: Bearer $BWT_API_KEY"
Example response
{
  "code": "BWT_SUCCESS",
  "patient": {"id":1,"displayName":"Personal record","selfUserId":1},
  "tests": [],
  "results": [],
  "clinicalNotes": "Patient context..."
}
GET/external/tests

List blood tests. Optional status query values are active (default), archived, and all.

Example request
curl "https://health-tracker.project4.net/api/external/tests?status=active" \
  -H "Authorization: Bearer $BWT_API_KEY"
Example response
[
  {
    "id": 123,
    "testName": "Haemoglobin",
    "groupName": "GENERAL HAEMATOLOGY",
    "unitOfMeasure": "g/L",
    "archivedAt": null,
    "archivedReason": "",
    "refRangeLow": 135,
    "refRangeHigh": 180,
    "targetZoneEnabled": true,
    "targetZoneLow": 145,
    "targetZoneHigh": 165,
    "targetZoneUnit": "g/L",
    "createdAt": "2026-05-06 10:00:00+00"
  }
]
POST/external/tests

Create a blood test.

Example request
curl -X POST https://health-tracker.project4.net/api/external/tests \
  -H "Authorization: Bearer $BWT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "testName": "Ferritin",
    "groupName": "IRON STUDIES",
    "unitOfMeasure": "ug/L",
    "refRangeLow": 30,
    "refRangeHigh": 400,
    "targetZoneEnabled": true,
    "targetZoneLow": 80,
    "targetZoneHigh": 180,
    "targetZoneUnit": "ug/L"
  }'
Request body
{
  "testName": "Ferritin",
  "groupName": "IRON STUDIES",
  "unitOfMeasure": "ug/L",
  "refRangeLow": 30,
  "refRangeHigh": 400,
  "targetZoneEnabled": true,
  "targetZoneLow": 80,
  "targetZoneHigh": 180,
  "targetZoneUnit": "ug/L"
}
Example response
{
  "code": "BWT_CREATED",
  "id": 124,
  "testName": "Ferritin",
  "groupName": "IRON STUDIES",
  "unitOfMeasure": "ug/L",
  "refRangeLow": 30,
  "refRangeHigh": 400,
  "targetZoneEnabled": true,
  "targetZoneLow": 80,
  "targetZoneHigh": 180,
  "targetZoneUnit": "ug/L",
  "createdAt": "2026-05-06 10:00:00+00"
}
GET/external/tests/{id}

Return one test and its results.

Example request
curl https://health-tracker.project4.net/api/external/tests/123 \
  -H "Authorization: Bearer $BWT_API_KEY"
Example response
{
  "code": "BWT_SUCCESS",
  "test": {
    "id": 123,
    "testName": "Haemoglobin",
    "groupName": "GENERAL HAEMATOLOGY",
    "unitOfMeasure": "g/L",
    "refRangeLow": 135,
    "refRangeHigh": 180,
    "targetZoneEnabled": true,
    "targetZoneLow": 145,
    "targetZoneHigh": 165,
    "targetZoneUnit": "g/L",
    "createdAt": "2026-05-06 10:00:00+00"
  },
  "results": [
    {
      "id": 456,
      "testId": 123,
      "testDate": "2026-05-06",
      "value": 151,
      "createdAt": "2026-05-06 10:00:00+00"
    }
  ]
}
PUT/external/tests/{id}

Replace a test definition.

Example request
curl -X PUT https://health-tracker.project4.net/api/external/tests/123 \
  -H "Authorization: Bearer $BWT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "testName": "Ferritin",
    "groupName": "IRON STUDIES",
    "unitOfMeasure": "ug/L",
    "refRangeLow": 30,
    "refRangeHigh": 400,
    "targetZoneEnabled": true,
    "targetZoneLow": 80,
    "targetZoneHigh": 180,
    "targetZoneUnit": "ug/L"
  }'
Example response
{
  "code": "BWT_SUCCESS",
  "id": 123,
  "testName": "Ferritin",
  "groupName": "IRON STUDIES",
  "unitOfMeasure": "ug/L",
  "refRangeLow": 30,
  "refRangeHigh": 400,
  "targetZoneEnabled": true,
  "targetZoneLow": 80,
  "targetZoneHigh": 180,
  "targetZoneUnit": "ug/L",
  "createdAt": "2026-05-06 10:00:00+00"
}
PATCH/external/tests/{id}

Update a test definition.

Example request
curl -X PATCH https://health-tracker.project4.net/api/external/tests/123 \
  -H "Authorization: Bearer $BWT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "testName": "Ferritin",
    "groupName": "IRON STUDIES",
    "unitOfMeasure": "ug/L",
    "refRangeLow": 30,
    "refRangeHigh": 400,
    "targetZoneEnabled": true,
    "targetZoneLow": 80,
    "targetZoneHigh": 180,
    "targetZoneUnit": "ug/L"
  }'
Example response
{
  "code": "BWT_SUCCESS",
  "id": 123,
  "testName": "Ferritin",
  "groupName": "IRON STUDIES",
  "unitOfMeasure": "ug/L",
  "refRangeLow": 30,
  "refRangeHigh": 400,
  "targetZoneEnabled": true,
  "targetZoneLow": 80,
  "targetZoneHigh": 180,
  "targetZoneUnit": "ug/L",
  "createdAt": "2026-05-06 10:00:00+00"
}
DELETE/external/tests/{id}

Delete a test and its results.

Example request
curl -X DELETE https://health-tracker.project4.net/api/external/tests/123 \
  -H "Authorization: Bearer $BWT_API_KEY"
Example response
{ "code": "BWT_OK", "ok": true }
GET/external/results

List results. Optional query parameters: testId, from, to, and status. Archived assay results are hidden unless status=archived or status=all is requested.

Example request
curl "https://health-tracker.project4.net/api/external/results?testId=123&from=2026-01-01&to=2026-12-31&status=active" \
  -H "Authorization: Bearer $BWT_API_KEY"
Example response
[
  {
    "id": 456,
    "testId": 123,
    "testDate": "2026-05-06",
    "value": 151,
    "createdAt": "2026-05-06 10:00:00+00"
  }
]
POST/external/results

Create or upsert a dated result.

Example request
curl -X POST https://health-tracker.project4.net/api/external/results \
  -H "Authorization: Bearer $BWT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "testId": 123,
    "testDate": "2026-05-06",
    "value": 151
  }'
Request body
{
  "testId": 123,
  "testDate": "2026-05-06",
  "value": 151
}
Example response
{
  "code": "BWT_CREATED",
  "id": 456,
  "testId": 123,
  "testDate": "2026-05-06",
  "value": 151,
  "createdAt": "2026-05-06 10:00:00+00"
}
GET/external/results/{id}

Return one result.

Example request
curl https://health-tracker.project4.net/api/external/results/456 \
  -H "Authorization: Bearer $BWT_API_KEY"
Example response
{
  "code": "BWT_SUCCESS",
  "id": 456,
  "testId": 123,
  "testDate": "2026-05-06",
  "value": 151,
  "createdAt": "2026-05-06 10:00:00+00"
}
PUT/external/results/{id}

Replace a result.

Example request
curl -X PUT https://health-tracker.project4.net/api/external/results/456 \
  -H "Authorization: Bearer $BWT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "testId": 123,
    "testDate": "2026-05-06",
    "value": 152
  }'
Example response
{
  "code": "BWT_SUCCESS",
  "id": 456,
  "testId": 123,
  "testDate": "2026-05-06",
  "value": 152,
  "createdAt": "2026-05-06 10:00:00+00"
}
PATCH/external/results/{id}

Update a result.

Example request
curl -X PATCH https://health-tracker.project4.net/api/external/results/456 \
  -H "Authorization: Bearer $BWT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "testId": 123,
    "testDate": "2026-05-06",
    "value": 152
  }'
Example response
{
  "code": "BWT_SUCCESS",
  "id": 456,
  "testId": 123,
  "testDate": "2026-05-06",
  "value": 152,
  "createdAt": "2026-05-06 10:00:00+00"
}
DELETE/external/results/{id}

Delete a result.

Example request
curl -X DELETE https://health-tracker.project4.net/api/external/results/456 \
  -H "Authorization: Bearer $BWT_API_KEY"
Example response
{ "code": "BWT_OK", "ok": true }
GET/external/clinical-notes

Return clinical notes.

Example request
curl https://health-tracker.project4.net/api/external/clinical-notes \
  -H "Authorization: Bearer $BWT_API_KEY"
Example response
{
  "code": "BWT_SUCCESS",
  "clinicalNotes": "Patient context..."
}
PUT/external/clinical-notes

Update clinical notes.

Example request
curl -X PUT https://health-tracker.project4.net/api/external/clinical-notes \
  -H "Authorization: Bearer $BWT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clinicalNotes": "Updated patient context..."
  }'
Request body
{ "clinicalNotes": "Updated patient context..." }
Example response
{
  "code": "BWT_SUCCESS",
  "clinicalNotes": "Updated patient context..."
}

Browser Archive Routes

These internal browser routes require a valid session cookie, not an API key. Both return 200/BWT_SUCCESS with a full browser bootstrap under state; common failures are 401 (session), 403 (patient access), 404 (assay). Cookie jars are secrets. Request examples modify data.

POST/tests/{id}/archive
Example request
curl -X POST https://health-tracker.project4.net/api/tests/123/archive \
  -b cookies.txt -H "Content-Type: application/json" \
  -d '{"archivedReason":"One-off assay"}'
Example response (state abbreviated)
{"code":"BWT_SUCCESS","state":{"patient":{"id":1,"displayName":"Personal record","selfUserId":1},"tests":[],"results":[]}}
POST/tests/{id}/unarchive
Example request
curl -X POST https://health-tracker.project4.net/api/tests/123/unarchive \
  -b cookies.txt -H "Content-Type: application/json" -d '{}'
Example response (state abbreviated)
{"code":"BWT_SUCCESS","state":{"patient":{"id":1,"displayName":"Personal record","selfUserId":1},"tests":[],"results":[]}}

Example

curl https://health-tracker.project4.net/api/external/tests \
  -H "Authorization: Bearer $BWT_API_KEY"

Markdown reference remains available at API-DOCS.md.