Patients and consents
Creating the identity a case is filed under, recording what the patient accepted, and honouring a deletion request.
A patient is the identity a case is filed under. The clinical chart lives on the other side of the seam and never crosses into this contract, so what you read here is what you sent plus the ids Cuvo minted.
Creating a patient
POST /v1/patients needs patients:write.
curl https://api.cuvo.co/v1/patients \
-H "Authorization: Bearer cuvo_sk_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: patient-ada-lovelace-1" \
-d '{
"first_name": "Ada",
"last_name": "Lovelace",
"date_of_birth": "1990-04-14",
"sex_at_birth": "female",
"email": "ada@example.com",
"phone": "+14155550123",
"address": {
"line1": "1 Market St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105"
},
"external_id": "crm-88213"
}'
Three fields have a shape worth knowing before you send them. phone is E.164, so one format
reaches the pharmacy, the clinician and the SMS rail. state is a two-letter USPS code, and states
are the licensure unit for the formulary, so check GET /v1/states and the states list on a
medication before you build a funnel around one. sex_at_birth is male or female, recorded
because prescribing and licensure both depend on it.
external_id is your own key, unique for your integration within the organization. Look a patient
up by it with GET /v1/patients?external_id=crm-88213 rather than keeping your own mapping.
PATCH /v1/patients/{id} takes any subset of the same fields. An empty body is refused, so a PATCH
always means something.
Consents
Consent is required before a case can be filed, and it is append-only: a new acceptance is a new
row, never an edit. Record each one with POST /v1/patients/{id}/consents, which needs
consents:write.
curl https://api.cuvo.co/v1/patients/pat_9f2b7c4a/consents \
-H "Authorization: Bearer cuvo_sk_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: consent-ada-telehealth-1" \
-d '{ "kind": "telehealth", "version": "2026-04-01", "accepted_at": "2026-09-08T14:02:11Z" }'
kind is telehealth or privacy, and a case needs a current one of each. version is the
version of the document the patient actually saw, so the clinician reviewing the case knows what was
agreed to. accepted_at defaults to the moment the request arrives; send it when you are recording
an acceptance that happened earlier in your own flow.
GET /v1/patients/{id}/consents needs consents:read and returns every consent recorded for the
patient, which is what you keep for your own records.
Reading back
GET /v1/patients/{id} and GET /v1/patients need patients:read. GET /v1/patients/{id}/cases lists every case filed for a patient and needs cases:read, since it is
case data behind a patient path.
Exports and deletion
POST /v1/patients/{id}/exports starts an export of everything held for a patient and returns a
job. The job comes back already completed, in both modes: a chart is bounded, so there is
nothing to wait for. Its url is /v1/exports/{id}/download, fetched with the same credential
and good for 24 hours, which is the life of the document links inside the bundle. Read the job
again at GET /v1/patients/{id}/exports/{job} or at GET /v1/exports/{id}, whichever id you
kept. All of them need patients:read.
The bundle holds the patient, their cases, consents, files, prescriptions and orders in the same
shapes the rest of this API publishes, plus a download link per document. Collect it as often as
you need inside the window: the link is not one-shot, and a retry after a dropped connection is
the case it exists for. After expires_at the bundle and those links are deleted together and the
download answers 410 gone; start another export.
POST /v1/patients/{id}/deletion_requests needs patients:write and is a request, not an erasure.
Contact data is removed and the chart is restricted, while clinical records are retained for the
period the patient's state requires. The response carries a retention sentence saying so, because
you have to be able to tell the patient what actually happens.