Write up an encounter
Open encounters, sign notes and read the antecedentes of a patient.
ClinicsOnly in clinic workspaces.
The clinical record says what was done to a person and why. It holds the encounter, its notes, the antecedentes, the alerts, the intake forms they answered and the chart. It's health data under article 2 g) of Chile's Ley 21.719, with its own regime in 16 bis. The contract here is narrower than anywhere else in the API.
Three access rules
- Everything demands
clinic_record:read(orclinic_record:writeto write) on top of the clinic scope.clinic:readis not enough. - No connected app reaches it:
403 CONNECTED_APP_SENSITIVE_DATA, with patient names allowed or not. - Every read is logged, in the same transaction as the response. A read that cannot write its access log fails.
The encounter and its notes
An encounter is one visit: who saw the patient, where and when. The notes hang off it.
curl "https://api.vitrinadev.com/api/v1/clinic/patients/$PACIENTE/encounters" \
-H "Authorization: Bearer $VITRINA_KEY"{
"data": {
"data": [
{
"id": "44444444-0000-4000-8000-000000000001",
"clinic_patient_id": "12121212-0000-4000-8000-000000000001",
"appointment_id": null,
"professional_id": "ffffffff-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"kind": "consulta",
"status": "closed",
"starts_at": "2026-09-15T21:52:49.352Z",
"closed_at": "2026-09-15T22:32:49.352Z",
"notes": [
{
"id": "55555555-0000-4000-8000-000000000001",
"kind": "evolucion",
"body": "Control de ortodoncia. Se ajusta arco superior.",
"author_user_id": null,
"author": {
"kind": "api_key",
"id": "c1c1c1c1-0000-4000-8000-000000000001",
"name": "Ficha clínica (migración)"
},
"signed_at": "2026-09-15T22:27:49.352Z",
"amends_note_id": null
}
]
}
]
}
}POST /clinic/patients/{id}/encounters opens one and POST /clinic/encounters/{id}/close closes it. The close is idempotent: closing twice answers the same thing.
Who wrote each line
author_user_id, and opened_by_user_id on the encounter, is the member who wrote and nobody else; with an API key it comes back null. author tells the three ways of writing into a record apart:
- a person in the panel:
author_user_idwith their id andauthornull; - a workspace API key:
authorwith the key and the name it had then, because revoking a key mustn't rewrite who signed a record; - a person through a credential: their id, plus
author.viawith the personal token or app they came in through, "Camila vía Claude".
A clinical narrative is written once and corrections are new rows: a wrongly recorded author can't be fixed later.
Signing, and why it cannot be edited afterwards
A note is written with POST /clinic/encounters/{id}/notes. While it's unsigned it's corrected in place with PATCH /clinic/notes/{id}. POST /clinic/notes/{id}/sign signs it, and from that moment the text is never touched again.
Correcting a signed note is POST /clinic/notes/{id}/amend, which creates a new row pointing at the previous one through amends_note_id and carrying its reason. The original stays where it was: the record has to say what was known when the decision was taken.
Antecedentes and alerts
GET /clinic/patients/{id}/antecedentes answers both at once, because whoever is about to see the patient needs both:
{
"data": {
"antecedentes": [],
"flags": [
{
"id": "13131313-0000-4000-8000-000000000001",
"kind": "alergia",
"label": "Alergia a penicilina",
"detail": null,
"severity": "severa",
"shown_on_agenda": true,
"recorded_source": "staff",
"resolved_at": null
}
]
}
}An antecedente is history: an underlying condition, a surgery, a medication. It's neither deleted nor edited. It's superseded with POST /clinic/antecedentes/{id}/supersede, which leaves the previous one visible with its date.
An alert (flag) is something to see now. shown_on_agenda: true is the one the clinic chose to paint over the appointment. POST /clinic/patients/{id}/flags raises one and POST /clinic/flags/{id}/resolve lifts it with a reason.
GET /clinic/record/agenda-flags answers the alerts of a whole page of patients at once, which is what the agenda needs to draw them without one call per appointment. It's a record operation: it asks for clinic_record:read and is marked sensitive. For the same reason, the flags the agenda includes on each appointment never reach a connected app, neither empty nor filled (Agenda).
Intake forms
An intake form is a questionnaire the patient answers before an appointment. Its questions aren't sensitive and live in Templates and retention. What one person answered is.
POST /clinic/patients/{id}/formsopens a form against the live version of the template and freezes it there. If the questions change tomorrow, this form still says which ones were answered.PATCH /clinic/forms/{id}saves answers while it's unfinished andPOST /clinic/forms/{id}/submitcloses them. After that onlyPOST /clinic/forms/{id}/amendremains, which creates a new response with its reason.POST /clinic/forms/{id}/linkmints the tokenised link the patient opens on their phone.GET /clinic/forms/pendingis "Fichas pendientes", what's owed before an upcoming appointment, andGET /clinic/forms/requirementsanswers whether that appointment can be confirmed.
The chart and its findings
GET /clinic/patients/{id}/charts lists the charts a patient has and GET /clinic/patients/{id}/charts/{kind} returns one, with its findings and its vocabulary. Saving the drawing (POST …/charts/{kind}/versions) creates a version, and earlier ones stay at GET /clinic/charts/{chartId}/versions.
A finding is recorded with POST /clinic/charts/{chartId}/findings, marked treated with resolve and undone with reopen. GET /clinic/chart-findings/{findingId}/plan-item says which budget line it would become, at today's price (Budgets and the till).
Who looked at this record
GET /clinic/patients/{id}/record-events returns one record's access log, and GET /clinic/patients/{id}/record-events/export the same thing as CSV. On top of the record scope they ask for clinic_admin:write: who looked at a record is reviewed by the clinic's administration.
Each line carries the actor, the route and the date. This is how a read by one of the clinic's API keys looks:
{
"route": "GET /clinic/patients/:id/antecedentes",
"actor_kind": "api_key",
"actor_id": "aaaa1111-0000-4000-8000-000000000015",
"action": "view",
"entity": "clinic_record",
"at": "2026-09-22T22:00:27.764Z"
}The CSV export does not go to a connected app
Neither this one nor any other. In a clinic, an exported, streamed or binary
response is answered to a connected app with
403 CONNECTED_APP_SENSITIVE_DATA, names allowed or not.
Events
| Event | When |
|---|---|
clinic_encounter.opened · .closed | An encounter was opened or closed |
clinic_note.signed | A note was signed; from there it's immutable |
clinic_note.amended | A signed one was corrected, with a new row and its reason |
All three arrive as a notice, with data_omitted: "sensitive", even when the subscription asked for the resource data. A note's body doesn't travel in a webhook. The notice carries the id and the url; whoever holds the scope reads it with their credential.