temetro is in beta and under active development. Expect things to change.
temetro

Patients

Create, read, update, transfer, and delete patient records.

Patient records are identified by their file number (MRN), unique within a clinic.

MethodPathPermission
GET/api/patientspatient:read
GET/api/patients/{fileNumber}patient:read
POST/api/patientspatient:write
PUT/api/patients/{fileNumber}patient:write
POST/api/patients/{fileNumber}/transferpatient:write
POST/api/patients/{fileNumber}/labslab:write
DELETE/api/patients/{fileNumber}/labslab:write
DELETE/api/patients/{fileNumber}patient:delete

Role-based scoping

Two roles see a filtered view, enforced server-side: doctors (without an additional elevated role) see only patients whose primary provider they are; reception sees demographics only — clinical fields (medications, problems, labs, encounters, vitals) are omitted from their responses and ignored in their writes.

Endpoints

List patients

curl -b cookies.txt http://localhost:4000/api/patients
const patients = await fetch('http://localhost:4000/api/patients', {
  credentials: 'include',
}).then((r) => r.json());

Returns an array of full patient objects (see below).

Get a patient

curl -b cookies.txt http://localhost:4000/api/patients/1042
{
  "fileNumber": "1042",
  "name": "Hodan Warsame",
  "age": 58,
  "sex": "F",
  "pcp": "Dr. Amina Yusuf",
  "primaryProviderId": "usr_abc123",
  "status": "active",
  "initials": "HW",
  "phone": "+252 61 555 0142",
  "bloodType": "O+",
  "alerts": ["Penicillin allergy"],
  "vitals": { "bp": "128/82", "hr": "74", "temp": "36.8", "spo2": "97", "takenAt": "2026-06-08 09:30" },
  "vitalsTrend": { "label": "BP (systolic)", "unit": "mmHg", "points": [134, 131, 128] },
  "allergies": [{ "substance": "Penicillin", "reaction": "Rash", "severity": "moderate" }],
  "medications": [{ "name": "Metformin", "dose": "500 mg", "frequency": "2× daily" }],
  "problems": [{ "label": "Type 2 diabetes", "since": "2019" }],
  "labs": [{ "name": "HbA1c", "value": "6.9%", "flag": "high", "takenAt": "2026-05-30" }],
  "labTrend": { "label": "HbA1c", "unit": "%", "points": [7.4, 7.1, 6.9] },
  "encounters": [
    { "date": "2026-05-30", "type": "Follow-up", "provider": "Dr. Amina Yusuf", "summary": "Diabetes review; HbA1c improving." }
  ]
}

404 if the file number doesn't exist in your active clinic.

Create a patient

POST /api/patients with the full record. Returns 201 and the created patient.

FieldTypeNotes
fileNumberstringDigits only; unique per clinic. Auto-assigned when empty
namestringRequired
agenumber0–150; defaults to 0 when omitted
sex"M" | "F"Defaults to "M" when omitted
status"active" | "inpatient" | "discharged"Defaults to "active"
initialsstring1–4 chars; derived from the name when omitted
sourceenummanual (default) | aiai rows show an "Added by AI" badge
pcpstringDisplay name of the primary care provider
primaryProviderIdstring | nullUser id of the responsible clinician
phonestringContact number (demographic; visible to reception)
bloodTypeA+ | A- | B+ | B- | AB+ | AB- | O+ | O- | ""Clinical PHI — redacted for reception
alertsstring[]Prominent warnings
allergiesarray{ substance, reaction, severity: mild|moderate|severe }
medicationsarray{ name, dose, frequency }
problemsarray{ label, since }
labsarray{ name, value, flag: normal|high|low|critical, takenAt }
encountersarray{ date, type, provider, summary }
vitalsobject{ bp, hr, temp, spo2, takenAt }
vitalsTrend, labTrendobject{ label, unit, points: number[] }

Only name is strictly required; the rest accept safe defaults so sparse records (e.g. from an AI import) still save. The import path stamps source: "ai", and a missing fileNumber is auto-assigned.

Update a patient

PUT /api/patients/{fileNumber} takes the same full payload as create (the app sends the whole record on edit) and returns the updated patient.

Transfer a patient

Reassign the patient to another clinician in the same clinic:

curl -b cookies.txt -X POST http://localhost:4000/api/patients/1042/transfer \
  -H "Content-Type: application/json" \
  -d '{ "providerId": "usr_def456" }'

400 if the provider isn't a member of the clinic; returns the updated patient.

Append lab results

POST /api/patients/{fileNumber}/labs appends one or more analysis results to the record without replacing anything else — unlike the full PUT update. It is gated by the dedicated lab:write permission (the Lab role and full clinicians), so lab staff can submit results without patient-edit rights.

FieldTypeNotes
labsarrayRequired. 1–50 entries
labs[].namestringRequired. Test name, e.g. "Hemoglobin"
labs[].valuestringRequired. e.g. "14.2 g/dL"
labs[].flagenumRequired. normal | high | low | critical
labs[].takenAtstringRequired. Display date, e.g. "Jun 11, 2026"
curl -b cookies.txt -X POST http://localhost:4000/api/patients/1042/labs \
  -H "Content-Type: application/json" \
  -d '{ "labs": [{ "name": "Hemoglobin", "value": "14.2 g/dL", "flag": "normal", "takenAt": "Jun 11, 2026" }] }'

Returns 201 with the full updated patient; 404 if the file number doesn't exist. New results are appended after the existing ones.

Delete a lab result

DELETE /api/patients/{fileNumber}/labs removes a single result from a patient's record. The lab has no id on the client, so it's identified by its fields.

FieldTypeNotes
namestringRequired. Test name
valuestringRequired. The recorded value
takenAtstringRequired. The recorded date
curl -b cookies.txt -X DELETE http://localhost:4000/api/patients/1042/labs \
  -H "Content-Type: application/json" \
  -d '{ "name": "Hemoglobin", "value": "14.2 g/dL", "takenAt": "Jun 11, 2026" }'

Returns 200 with the updated patient; 404 if the file number doesn't exist. Gated by lab:write (like appending), so lab staff can correct their own entries.

Delete a patient

curl -b cookies.txt -X DELETE http://localhost:4000/api/patients/1042

Returns 204. Requires patient:delete (owner or admin).

Every mutation is written to the activity log and notifies the clinic's members in real time.

On this page