Skip to content

Patient and prescription API contracts ​

Current implementation ​

Patient CRUD is implemented through Doctor-owned /api/v1/doctors/patients routes with persistence, authentication, organization access, subscription write gates, optimistic versions and audit. See the Doctor patient workflow for the active portal contract and provisioning.

The seven Prescription clinical operations below remain documentation-first Huma contracts: malformed JSON returns 400, invalid contracts return 422, and valid requests return 501. Their clinical lifecycle and storage are deferred. Patient remains a separate service with no dependency on Prescription; its public reader now performs authorized profile lookups.

The old in-memory prescription HTTP handlers are replaced. PUT draft updates and DELETE cancellation are removed; use PATCH and POST cancellation below. Existing in-memory persistence scaffolding remains unused for clinical documents. These clinical operations do not add independent deployments, frontend changes, or lab-result workflows. The separate global suggestion catalogs have implemented PostgreSQL-backed CRUD and their own migrations.

Operations ​

MethodPathIntended success
POST/api/v1/doctors/patients201 Patient
GET/api/v1/doctors/patients200 Page of Patient
GET/api/v1/doctors/patients/{patientId}200 Patient
PATCH/api/v1/doctors/patients/{patientId}200 Patient
POST/api/v1/prescriptions201 Prescription
GET/api/v1/prescriptions200 Page of Summary
GET/api/v1/prescriptions/{prescriptionId}200 Prescription
PATCH/api/v1/prescriptions/{prescriptionId}200 Prescription
POST/api/v1/prescriptions/{prescriptionId}/issue200 Prescription
POST/api/v1/prescriptions/{prescriptionId}/cancel200 Prescription
GET/api/v1/prescriptions/patient-history/{patientId}200 PatientPrescriptionHistory

Protected operations declare the existing bearerAuth scheme. For Prescription clinical stubs the header remains optional: valid requests with no credentials or a placeholder bearer token still return 501. No endpoint returns a success example as if it were persisted data.

Minimal registration and creation ​

Register a patient using organization context, name, age and sex:

json
{"organization_id":"11111111-1111-4111-8111-111111111111","name":"Patient name","age":"25 years","sex":"unknown"}

Name, age and sex are required. Age is a nonblank string accepting any text, without numeric or unit validation. Sex must be male, female, other or unknown; no value is supplied implicitly. Email, phone, blood group and a single address string are optional. Birth date, allergies and chronic conditions are removed from profiles and the shared patient snapshot contract. Patient ID, organization-scoped MRN, creator identity and timestamps are server-owned. Duplicate names and phone numbers do not automatically merge or reject patients.

Patient records age_recorded_at on creation and when normalized age changes. PATCH preserves omitted fields and permits null to clear optional fields. Name, age and sex cannot be null or blank when supplied. PATCH requires expected_version plus an editable field. Patient ID, MRN and organization are immutable. Doctor HTTP requires a professional profile, exact affiliation and scoped grants.

Start a new consultation with:

json
{"patient_id":"patient_123"}

The future implementation loads the authorized patient using PatientReader, derives their organization, and resolves the authoring doctor from authentication. Never accept doctor identity or organization from spoofable X-User-ID/X-Org-ID headers. Patient and organization references are immutable. Each new consultation receives its own prescription ID and version 1. Multiple drafts for a patient are allowed; patient-keyed singleton drafts and speculative /drafts routes are not part of this contract. Encounter date defaults to the server's current date; visit reason, specialty ID, notes and all clinical sections are optional in a draft.

Clinical content and PATCH ​

The full workspace contract retains ordered complaints and detailed qualifiers, custom clarification answers, positive/negative history, textual observations, investigation orders, diagnoses, medication orders with structured SIG, advice, and follow-up. Optional catalog references are stored independently from their labels; manually named medicines remain representable without a catalog API. Medication SIG preserves frequency, finite/ongoing duration, meal relation, timing and instruction. Drafts may have incomplete directions. Ongoing and finite duration cannot be supplied together. Relative follow-up requires value and unit; it cannot be combined with an exact date. Clinical duration units are singular hour, day, week, month, and year; patient age is independent free text.

Complete workspace example is embedded into the OpenAPI request examples and exercised by contract tests.

Every prescription edit, issue and cancel requires expected_version:

json
{"expected_version":3,"advice":[{"id":"advice_1","text":"Rest"}]}

A supplied array replaces that entire section; [] clears it. Omitted sections remain unchanged. Follow-up is replaced as a whole, with null clearing it. Nullable visit reason, specialty and notes can also be cleared. At least one editable field is required alongside the version. Null arrays are invalid. Clinical entry IDs are local identifiers within the document and ordering is preserved.

Supplied maps on public PATCH DTOs retain explicit null versus absence and are excluded from JSON/OpenAPI. Use those maps, not pointer nil alone, when applying future patches. Nullable object schemas use anyOf with null because the pinned Huma version does not support nullable tags on referenced objects. A schema-aware HTTP check also rejects null for non-nullable optional fields that Huma otherwise skips validating, including nested clinical arrays.

The intended implementation atomically compares versions and increments on success. Stale versions return 409. Clients update their local version from the mutation response, serialize/debounce saves, and refetch rather than blindly retry stale operations. No concurrency behavior is implemented by the stubs.

Lifecycle, sharing and history ​

Allowed transitions are draft to issued, draft to canceled, and issued to canceled. Only the authoring doctor with active organization affiliation can mutate records. Issuance requires a linked patient and at least one diagnosis, medication, investigation or advice entry. Every medicine requires a frequency or meaningful free-text instruction. These checks require persisted state and are TODO rules, not validation of the issue request body's version alone.

Issuance freezes the ordered clinical content, patient and doctor display snapshots, and issued_at. Later profile changes must not rewrite issued documents. Canceling requires a nonblank reason and expected_version, retaining content, snapshots and issuance metadata while recording cancellation actor/time/reason. Repeat issue or cancel attempts return 409; after ambiguous network responses, read current state. Corrections require canceling the original and creating a new draft. Printing is a frontend action, separate from issuance.

Active authorized organization members share issued/canceled history. Drafts are author-private. Patient and prescription access requires active identity and membership/affiliation. Out-of-scope identifiers return 404; known forbidden actions return 403. Authentication returns 401 once implemented. State/version conflicts return 409, and incomplete persisted clinical content at issuance returns 422. Unexpected errors return a generic 500 without internal details.

Both collection APIs require organization_id. Patients accept optional search (or the deprecated q alias) across name, ID, MRN, email, phone and address. Prescriptions accept patient_id, doctor_id and status. Prescription lists use {items, next_cursor?}; Patient lists use the success envelope with a data array and pagination containing next_cursor, current_page, per_page, total_pages and total_items. Both default to limit 20/max 100, newest first by created_at and ID descending. Cursors are opaque and bound to filters; Patient cursors also bind page size and current read access. Patient totals count only authorized profiles matching the search. Unknown query parameters are rejected on these contracts.

History is prescription-owned: its visits page contains issued/canceled summaries, including visible cancellation status, while recent observations derive only from issued records. Read prescription details to obtain previous medication/advice content and copy selected entries into a new draft without changing the source. There is no separate encounter repository, external lab result model, or claim that investigation orders are pending/completed.

Frontend mapping and verification ​

Reference frontend behavior/typeAPI integration
Patient selection and registrationPatient search/detail plus POST patient with name, age and sex; never invent demographic values
Patient ageSend and display the reported age string without parsing or appending a unit
Patient.date / visitReasonPrescription encounter_date / visit_reason, not profile fields
camelCase clinical objectsConvert keys to snake_case recursively, including complaint details and SIG
patientId / medicationId / displayName / followUppatient_id / medication_id / display_name / follow_up
Patient-keyed local draft restoreList author drafts filtered by patient, then restore a selected prescription ID
Full draft autosavePATCH changed sections with current expected_version
Previous Rx and encounter drawersPrescription history summaries and prescription detail; canceled status remains visible
Patient previous lab results/pending investigationsDeferred; do not invent data from prescription orders
Frontend array list responsesPatient: unwrap data and pagination.next_cursor; Prescription: items/next_cursor
Browser printRemains local and does not implicitly issue

This release does not change frontend services or local storage. A configured remote request returning 501 must not be mistaken for a saved server record. Global diagnosis, advice, history and investigation lookup catalogs are available through the prescription suggestion routes. Templates, layout settings, patient merging/deletion, lab entry and patient account connections remain outside this release.

Run go run ./tools/cli generate openapi to refresh the static document; both runtime OpenAPI and Scalar/Swagger use the existing tooling. Relevant checks include:

sh
go test -race -short ./services/patient/... ./services/prescription/... ./services/api/...
make test
test -z "$(gofmt -l -s pkg services tools)"
make lint

Contract tests cover all routes, minimal creates, validation errors, PATCH presence/clearing and context forwarding, rich clinical content round trips, OpenAPI schemas/examples, generated/runtime parity, and removed legacy mutations. Future functional work must additionally test organization isolation, author-only writes, draft privacy, persisted issuance requirements, immutable snapshots, version races, cancellation retention, transactional persistence and post-commit events. None of those business scenarios is claimed as implemented here.

Documentation follows the source. Planned contracts are marked explicitly.