Patient Chart Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · API_PATH_CONVENTIONS · 05 API Design · FHIR-First
All responses use platform envelope { data, error?, meta? }; errors conform to ERROR_CODES.md. Authentication: Bearer JWT from identity-service. Standard headers: Authorization, X-Tenant-Id (also in JWT), Idempotency-Key on POST mutations, If-Match on PATCH using the aggregate version.
1. Product REST — Problem list
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /v1/problems | chart:problems:write | Create a problem |
| GET | /v1/problems | chart:problems:read | List problems for a patient (?patientId=&status=&category=) |
| GET | /v1/problems/{problemId} | chart:problems:read | Fetch one |
| PATCH | /v1/problems/{problemId} | chart:problems:write | Patch attributes |
| POST | /v1/problems/{problemId}/resolve | chart:problems:write | Resolve |
| POST | /v1/problems/{problemId}/inactivate | chart:problems:write | Inactivate |
| POST | /v1/problems/{problemId}/entered-in-error | chart:problems:admin | Entered-in-error |
| POST | /v1/problems/{problemId}/links | chart:problems:write | Link to encounter/note/goal/order |
1.1 POST /v1/problems — request
{
"patientId": "pat_01H...",
"code": { "codings": [{ "system": "http://snomed.info/sct", "code": "44054006", "display": "Diabetes mellitus type 2" }], "text": "T2DM" },
"clinicalStatus": "active",
"verificationStatus": "confirmed",
"onsetDate": "2024-06-12",
"encounterId": "enc_01H...",
"severity": "moderate",
"notes": "Patient reports elevated fasting glucose over 6 months."
}
1.2 Response 201
{
"data": {
"id": "prb_01HZYXW...",
"version": 1,
"status": "active",
"clinicalStatus": "active",
"verificationStatus": "confirmed",
"code": { "codings": [...], "text": "T2DM" },
"onsetDate": "2024-06-12",
"createdAt": "2026-04-17T08:12:03Z",
"createdBy": { "id": "prc_01H...", "display": "Dr. Nabil" }
}
}
Errors: 422 CHART_DUPLICATE_PROBLEM, 422 CHART_CROSS_TENANT_REFERENCE, 409 CHART_INVALID_VERSION, 401/403, 404 PATIENT_NOT_FOUND.
2. Product REST — Allergies
| Method | Path | Scope |
|---|---|---|
| POST | /v1/allergies | chart:allergies:write |
| GET | /v1/allergies | chart:allergies:read |
| GET | /v1/allergies/{allergyId} | chart:allergies:read |
| PATCH | /v1/allergies/{allergyId} | chart:allergies:write |
| POST | /v1/allergies/{allergyId}/inactivate | chart:allergies:write |
| POST | /v1/allergies/{allergyId}/entered-in-error | chart:allergies:admin |
| POST | /v1/allergies/assertions/nka | chart:allergies:write |
| POST | /v1/allergies/assertions/nkda | chart:allergies:write |
| GET | /v1/allergies/advisory | chart:allergies:advisory (internal) |
2.1 Allergy create
{
"patientId": "pat_01H...",
"substance": { "codings": [{ "system": "http://www.nlm.nih.gov/research/umls/rxnorm", "code": "11124", "display": "Penicillin" }] },
"categories": ["medication"],
"clinicalStatus": "active",
"verificationStatus": "confirmed",
"reactions": [
{ "manifestation": [{ "codings": [{ "system": "http://snomed.info/sct", "code": "247472004", "display": "Hives" }] }], "severity": "moderate" }
]
}
2.2 Advisory (internal, called by orders/medication)
GET /v1/allergies/advisory?patientId=pat_01H...&rxnormCode=11124
{ "data": { "matches": [{ "allergyId": "alg_...", "severity": "severe", "substance": { ... }, "matchType": "coded" }], "highestSeverity": "severe" } }
3. Product REST — Vitals
| Method | Path | Scope |
|---|---|---|
| POST | /v1/vitals | chart:vitals:write |
| GET | /v1/vitals | chart:vitals:read — ?patientId=&code=&from=&to=&limit= |
| GET | /v1/vitals/{vitalsSetId} | chart:vitals:read |
| POST | /v1/vitals/{vitalsSetId}/corrections | chart:vitals:write |
| GET | /v1/vitals/trend | chart:vitals:read — ?patientId=&code=&range=7d |
3.1 Vitals record
{
"patientId": "pat_01H...",
"recordedAt": "2026-04-17T08:45:00Z",
"encounterId": "enc_01H...",
"collectionLocationId": "loc_01H...",
"method": "manual",
"measurements": [
{ "code": { "codings": [{ "system": "http://loinc.org", "code": "8480-6" }] }, "value": 142, "unit": "mm[Hg]" },
{ "code": { "codings": [{ "system": "http://loinc.org", "code": "8462-4" }] }, "value": 92, "unit": "mm[Hg]" },
{ "code": { "codings": [{ "system": "http://loinc.org", "code": "8867-4" }] }, "value": 88, "unit": "/min" }
]
}
Response includes per-measurement abnormalFlag when out of configured ranges.
4. Product REST — Clinical notes
| Method | Path | Scope |
|---|---|---|
| POST | /v1/clinical-notes | chart:notes:write |
| GET | /v1/clinical-notes | chart:notes:read — ?patientId=&type=&author=&status=&from=&to= |
| GET | /v1/clinical-notes/{noteId} | chart:notes:read |
| PATCH | /v1/clinical-notes/{noteId} | chart:notes:write (draft only) |
| POST | /v1/clinical-notes/{noteId}/sign | chart:notes:sign |
| POST | /v1/clinical-notes/{noteId}/cosign-request | chart:notes:sign |
| POST | /v1/clinical-notes/{noteId}/cosign | chart:notes:cosign |
| POST | /v1/clinical-notes/{noteId}/addenda | chart:notes:write |
| POST | /v1/clinical-notes/{noteId}/entered-in-error | chart:notes:admin |
| POST | /v1/clinical-notes/{noteId}/ai-accepted | chart:notes:write |
| POST | /v1/clinical-notes/{noteId}/read | chart:notes:read (idempotent) |
4.1 Note create
{
"patientId": "pat_01H...",
"encounterId": "enc_01H...",
"templateId": "soap.v1",
"sections": [
{ "key": "subjective", "text": "Patient presents with ..." },
{ "key": "objective", "text": "BP 142/92 ..." },
{ "key": "assessment", "text": "Uncontrolled T2DM." },
{ "key": "plan", "text": "Increase metformin; labs in 1 week." }
]
}
4.2 Sign
POST /v1/clinical-notes/{noteId}/sign with { "version": 3, "signatureMethod": "password" } — response either status=signed immediately or status=pending_cosign with pendingCosigners[].
5. Product REST — Chart composition
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /v1/chart/{patientId}/banner | chart:read | Header payload |
| GET | /v1/chart/{patientId}/summary | chart:read | ?widgets=problems,allergies,vitals,meds,labs,imm,care-plan,orders |
| GET | /v1/chart/{patientId}/timeline | chart:read | ?cursor=&types=notes,orders,results,meds,vitals&from=&to= |
| POST | /v1/chart/{patientId}/snapshot/export | chart:export | `{ format: "pdf" |
| POST | /v1/chart/{patientId}/breakglass | chart:breakglass | { reason, durationMinutes } |
5.1 Summary response (abbreviated)
{
"data": {
"patient": { "id": "...", "displayName": "...", "dob": "...", "sex": "..." },
"widgets": {
"problems": { "count": 4, "items": [{ "id": "prb_...", "display": "T2DM", "status": "active" }] },
"allergies": { "count": 2, "items": [...] },
"vitals": { "latestSet": { ... } },
"meds": { "count": 3, "items": [...] },
"labs": { "items": [...] },
"imm": { "items": [...] },
"care-plan": { "active": [...] }
},
"provenance": { "problems": "patient-chart", "meds": "medication-service", "labs": "laboratory-service" }
}
}
5.2 Timeline — cursor pagination
Request: GET /v1/chart/{patientId}/timeline?types=notes,vitals,meds&limit=50.
Response has data[] (sorted descending by occurredAt) and meta.nextCursor (opaque base64 with {occurredAt, tiebreaker}). Follow-up: ?cursor=...&limit=50.
6. FHIR R4 read surface
Served by patient-chart-service at /fhir/R4/*, fronted by interop-service / fhir-gateway.
| Resource | Read | Search params (at minimum) |
|---|---|---|
Condition | GET /fhir/R4/Condition/{id}, GET /fhir/R4/Condition?... | patient, clinical-status, verification-status, category, onset-date, code, _sort=-onset-date, _count |
AllergyIntolerance | GET /fhir/R4/AllergyIntolerance?... | patient, clinical-status, verification-status, category, code, _lastUpdated |
Observation (vital-signs) | GET /fhir/R4/Observation?... | patient, category=vital-signs, code, date, _sort=-date, _count |
Composition | GET /fhir/R4/Composition?... | subject, type, status, date, _sort=-date |
DocumentReference | GET /fhir/R4/DocumentReference?... | subject, type, status, period, _sort=-date |
Writes via FHIR HTTP are out of scope for this release; creation/update happens only via /v1/* REST.
7. Error schema
{
"error": {
"code": "CHART_NKA_CONFLICT",
"message": "Active NKA record exists; cannot add substance allergy until NKA is inactivated.",
"details": { "nkaAllergyId": "alg_01H..." },
"correlationId": "c3f0..."
}
}
8. Rate limits
| Endpoint class | Limit |
|---|---|
Mutations (POST/PATCH) | 60/min per user |
| Reads (aggregate) | 600/min per user |
| Advisory (internal) | 3000/min per service principal |
9. Pagination
All list endpoints support cursor pagination: ?cursor= + ?limit= (max 200); response includes meta.nextCursor. Offset pagination is not supported (chart timelines are unbounded).
10. Open Questions
- Should
/v1/chart/{patientId}/timelinesupport server-side configured defaulttypesper role? - FHIR
$everythingoperation: MVP out of scope; to be added when interop-service merges the composition bundle.