Skip to content

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.