VitrinaAPI

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

  1. Everything demands clinic_record:read (or clinic_record:write to write) on top of the clinic scope. clinic:read is not enough.
  2. No connected app reaches it: 403 CONNECTED_APP_SENSITIVE_DATA, with patient names allowed or not.
  3. 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_id with their id and author null;
  • a workspace API key: author with 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.via with 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}/forms opens 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 and POST /clinic/forms/{id}/submit closes them. After that only POST /clinic/forms/{id}/amend remains, which creates a new response with its reason.
  • POST /clinic/forms/{id}/link mints the tokenised link the patient opens on their phone.
  • GET /clinic/forms/pending is "Fichas pendientes", what's owed before an upcoming appointment, and GET /clinic/forms/requirements answers 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

EventWhen
clinic_encounter.opened · .closedAn encounter was opened or closed
clinic_note.signedA note was signed; from there it's immutable
clinic_note.amendedA 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.

On this page