VitrinaAPI

Clinical record

What was done to a person and why: encounters, signed notes, antecedentes, alerts, intake forms and the chart with its history. Marked sensitive.

Beta
This may change at any time, with a changelog entry and a notice to recent callers — see versioning.

Download the full API pública projection: openapi.json.

Write up an encounter explains this resource in prose, with runnable examples.

MethodPathWhat it does
POST/clinic/antecedentes/{id}/supersedeSupersede an antecedente with a successor
POST/clinic/appointments/{id}/attend«Atender»: open the cita's atención and move it into the box
GET/clinic/chart-findings/{findingId}/plan-itemThe presupuesto line this finding would become
POST/clinic/chart-findings/{findingId}/plan-itemLink a finding to the plan item (or cita line) it produced
POST/clinic/chart-findings/{findingId}/reopenUndo «tratado» on a finding
POST/clinic/chart-findings/{findingId}/resolveMark a finding treated
GET/clinic/charts/{chartId}/findingsThe findings painted on one chart
POST/clinic/charts/{chartId}/findingsRecord one finding without redrawing the chart
GET/clinic/charts/{chartId}/versionsThe chart’s version timeline
GET/clinic/charts/{chartId}/versions/{version}One past state of a chart
GET/clinic/encounters/{id}One atención with its evoluciones
POST/clinic/encounters/{id}/closeClose an atención (idempotent)
GET/clinic/encounters/{id}/notesEvoluciones of one atención, oldest first
POST/clinic/encounters/{id}/notesWrite an evolución
PUT/clinic/encounters/{id}/sterilization-loteRecord the sterilised-pack lote used on an atención
POST/clinic/encounters/{id}/voidVoid an atención (idempotent)
POST/clinic/flags/{id}/resolveLift a patient alert
GET/clinic/forms/{id}One filled ficha, with the questions it was answered against
PATCH/clinic/forms/{id}Save answers into an unfinished ficha
POST/clinic/forms/{id}/amendAmend a submitted ficha (a NEW response, with a reason)
POST/clinic/forms/{id}/linkMint the tokenised link the patient fills on their phone
POST/clinic/forms/{id}/submitSubmit a ficha (the last write that ever touches its answers)
GET/clinic/forms/pending«Fichas pendientes» — intake forms owed before an upcoming cita
GET/clinic/forms/requirementsIs this cita confirmable? What is still missing
PATCH/clinic/notes/{id}Correct an UNSIGNED evolución in place
POST/clinic/notes/{id}/amendAmend a signed evolución (a NEW row, never an edit)
POST/clinic/notes/{id}/signSign an evolución
GET/clinic/patients/{id}/antecedentesAntecedentes and patient alerts
POST/clinic/patients/{id}/antecedentesRecord an antecedente
GET/clinic/patients/{id}/chart-findingsEverything found on this patient, across every chart
GET/clinic/patients/{id}/chartsThe specialty charts a patient has
GET/clinic/patients/{id}/charts/{kind}One chart: its current state, its findings and its vocabulary
POST/clinic/patients/{id}/charts/{kind}/versionsSave the chart — an edit creates a version
GET/clinic/patients/{id}/encountersAtenciones of one patient, newest first, with their notes
POST/clinic/patients/{id}/encountersOpen an atención
POST/clinic/patients/{id}/flagsRaise a patient alert
GET/clinic/patients/{id}/formsFichas filled for one patient, newest first
POST/clinic/patients/{id}/formsOpen a ficha against the template’s live version
GET/clinic/patients/{id}/historyA patient's clinical history (Reservo)
GET/clinic/patients/{id}/prescriptionsThe recetas on one patient’s ficha
POST/clinic/patients/{id}/prescriptionsWrite a receta (and optionally sign it)
GET/clinic/patients/{id}/record-eventsWho looked at this clinical record
GET/clinic/patients/{id}/record-events/exportThe access log as CSV
GET/clinic/prescriptions/{id}One receta
PATCH/clinic/prescriptions/{id}Edit a DRAFT receta
POST/clinic/prescriptions/{id}/discardDiscard a DRAFT receta
GET/clinic/prescriptions/{id}/pdfThe receta as a PDF
POST/clinic/prescriptions/{id}/sendSend a signed receta to the patient (WhatsApp or email)
GET/clinic/prescriptions/{id}/sendsWhen and how a receta was sent to the patient
POST/clinic/prescriptions/{id}/signSign (issue) a receta
GET/clinic/record/agenda-flagsAgenda-shown alerts for a page of patients

POST /clinic/antecedentes/{id}/supersede

Supersede an antecedente with a successor

Inserts the successor and stamps superseded_at / superseded_by on the predecessor — and NOTHING else on it. Omit successor to retire an item that was simply wrong; it is still never deleted. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
reasonstring | null—máx. 500
successorobject | null—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/antecedentes/<id>/supersede \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Confirmada por el alergólogo, con severidad distinta",
    "successor": {
      "kind": "alergia",
      "label": "Alergia a penicilina",
      "detail": "Confirmada por test cutáneo",
      "severity": "critica"
    }
  }'

Example response (200)

{
  "data": {
    "superseded": {
      "id": "02db347c-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "kind": "alergia",
      "label": "Alergia a penicilina",
      "detail": "Referida por la paciente; reacción cutánea",
      "severity": "severa",
      "status": "active",
      "onset_on": null,
      "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
      "recorded_source": "staff",
      "superseded_at": "2026-09-22T22:21:17.500Z",
      "superseded_by": "a455d934-0000-4000-8000-000000000001",
      "superseded_reason": "Confirmada por el alergólogo, con severidad distinta",
      "created_at": "2026-09-22T22:21:17.476Z"
    },
    "successor": {
      "id": "a455d934-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "kind": "alergia",
      "label": "Alergia a penicilina",
      "detail": "Confirmada por test cutáneo",
      "severity": "critica",
      "status": "active",
      "onset_on": null,
      "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
      "recorded_source": "staff",
      "superseded_at": null,
      "superseded_by": null,
      "superseded_reason": null,
      "created_at": "2026-09-22T22:21:17.496Z"
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/appointments/{id}/attend

«Atender»: open the cita's atención and move it into the box

One call from a cita to its clinical record. Resolves the cita's patient (its own ficha, else the contact's), opens the atención LINKED to the cita — IDEMPOTENT: a second call returns the same atención — with the cita's professional, sede and hour, and moves the sala de espera to in_room when the patient is still scheduled or waiting and the caller holds clinic:write. Answers \{ encounter, clinic_patient_id, contact_id, arrival_state \}. Requires clinic_record:write AND the per-patient PHI gate; refuses a bloqueo, a cancelled or no-show cita, and a cita with no ficha. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/appointments/<id>/attend \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "encounter": {
      "id": "e0e57121-0000-4000-8000-0000000007ac",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
      "professional_id": "18342d1b-0000-4000-8000-000000000001",
      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
      "treatment_plan_id": null,
      "form_template_id": null,
      "sterilization_lote": null,
      "kind": "control",
      "status": "open",
      "starts_at": "2026-10-09T18:00:00.000Z",
      "ends_at": null,
      "closed_at": null,
      "opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
      "closed_by_user_id": null,
      "voided_reason": null,
      "author": null,
      "created_at": "2026-10-09T18:00:00.000Z",
      "updated_at": "2026-10-09T18:00:00.000Z",
      "notes": []
    },
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "contact_id": "c0c0c0c0-0000-4000-8000-000000000001",
    "arrival_state": "in_room"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/chart-findings/{findingId}/plan-item

The presupuesto line this finding would become

The draft a treatment-plan item is built from, carrying the tooth or zone so the fact survives to clinic_appointment_service.tooth_or_zone. The plan item itself belongs to the presupuestos module (T-33); this endpoint is the seam between the two.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
findingIdpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/chart-findings/<id>/plan-item \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
    "chart_kind": "odontograma",
    "notation": "fdi",
    "finding_id": "e29e8d94-0000-4000-8000-000000000001",
    "tooth_or_zone": "26",
    "surfaces": [
      "O"
    ],
    "code": "caries",
    "label": "Caries oclusal",
    "note": null,
    "treatment_plan_item_id": null
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/chart-findings/{findingId}/plan-item

Link a finding to the plan item (or cita line) it produced

Records the clinic_treatment_plan_item a finding became, so the chart can say «ya está en el presupuesto» instead of offering the same tooth twice, and/or the clinic_appointment_service it was finally performed on. Pass null to unlink.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
findingIdpathuuidyes

Body

FieldTypeRequiredConstraints
treatment_plan_item_idstring | null—
clinic_appointment_service_idstring | null—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/chart-findings/<id>/plan-item \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "treatment_plan_item_id": null
  }'

Example response (200)

{
  "data": {
    "id": "e29e8d94-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
    "tooth_or_zone": "26",
    "surfaces": [
      "O"
    ],
    "code": "caries",
    "label": "Caries oclusal",
    "note": null,
    "status": "active",
    "first_seen_version": 1,
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "treatment_plan_item_id": null,
    "clinic_appointment_service_id": null,
    "created_by_user_id": "11111111-0000-4000-8000-000000000001",
    "treated_at": null,
    "treated_by_user_id": null,
    "created_at": "2026-09-22T22:21:17.577Z",
    "updated_at": "2026-09-22T22:21:17.604Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/chart-findings/{findingId}/reopen

Undo «tratado» on a finding

Undo «tratado» on a finding

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
findingIdpathuuidyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/chart-findings/<id>/reopen \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "e29e8d94-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
    "tooth_or_zone": "26",
    "surfaces": [
      "O"
    ],
    "code": "caries",
    "label": "Caries oclusal",
    "note": null,
    "status": "active",
    "first_seen_version": 1,
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "treatment_plan_item_id": null,
    "clinic_appointment_service_id": null,
    "created_by_user_id": "11111111-0000-4000-8000-000000000001",
    "treated_at": null,
    "treated_by_user_id": null,
    "created_at": "2026-09-22T22:21:17.577Z",
    "updated_at": "2026-09-22T22:21:17.622Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/chart-findings/{findingId}/resolve

Mark a finding treated

The row stays: a treated caries is history, not noise, and the chart renders it differently rather than forgetting it.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
findingIdpathuuidyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/chart-findings/<id>/resolve \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "e29e8d94-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
    "tooth_or_zone": "26",
    "surfaces": [
      "O"
    ],
    "code": "caries",
    "label": "Caries oclusal",
    "note": null,
    "status": "treated",
    "first_seen_version": 1,
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "treatment_plan_item_id": null,
    "clinic_appointment_service_id": null,
    "created_by_user_id": "11111111-0000-4000-8000-000000000001",
    "treated_at": "2026-09-22T22:21:17.614Z",
    "treated_by_user_id": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-22T22:21:17.577Z",
    "updated_at": "2026-09-22T22:21:17.613Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/charts/{chartId}/findings

The findings painted on one chart

The findings painted on one chart

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
chartIdpathuuidyes
statusqueryactive \treatedno
curl https://api.vitrinadev.com/api/v1/clinic/charts/<id>/findings \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "e29e8d94-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "chart_id": "b82b40b8-0000-4000-8000-000000000001",
        "tooth_or_zone": "26",
        "surfaces": [
          "O"
        ],
        "code": "caries",
        "label": "Caries oclusal",
        "note": null,
        "status": "active",
        "first_seen_version": 1,
        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
        "treatment_plan_item_id": null,
        "clinic_appointment_service_id": null,
        "created_by_user_id": "11111111-0000-4000-8000-000000000001",
        "treated_at": null,
        "treated_by_user_id": null,
        "created_at": "2026-09-22T22:21:17.577Z",
        "updated_at": "2026-09-22T22:21:17.577Z"
      }
    ],
    "total": 1
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/charts/{chartId}/findings

Record one finding without redrawing the chart

The «a tratar» click: one tooth or zone, one code, optionally the surfaces. The target is validated against the chart’s notation.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
chartIdpathuuidyes

Body

FieldTypeRequiredConstraints
tooth_or_zonestringyesmín. 1, máx. 40
codestringyesmín. 1, máx. 80
surfacesstring[]—
labelstring | null—máx. 200
notestring | null—máx. 2000
encounter_idstring | null—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/charts/<id>/findings \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tooth_or_zone": "26",
    "code": "caries",
    "surfaces": [
      "O"
    ],
    "label": "Caries oclusal",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001"
  }'

Example response (201)

{
  "data": {
    "id": "e29e8d94-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
    "tooth_or_zone": "26",
    "surfaces": [
      "O"
    ],
    "code": "caries",
    "label": "Caries oclusal",
    "note": null,
    "status": "active",
    "first_seen_version": 1,
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "treatment_plan_item_id": null,
    "clinic_appointment_service_id": null,
    "created_by_user_id": "11111111-0000-4000-8000-000000000001",
    "treated_at": null,
    "treated_by_user_id": null,
    "created_at": "2026-09-22T22:21:17.577Z",
    "updated_at": "2026-09-22T22:21:17.577Z"
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/charts/{chartId}/versions

The chart’s version timeline

Every saved state of one chart, newest first, with its author, the atención it was saved in and the summary its author wrote. The client diffs two versions to show what changed. Imported versions (the history carried across from Dentalink) are the oldest — the end of this newest-first list — and carry imported_from / imported_as_of.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
chartIdpathuuidyes
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
curl https://api.vitrinadev.com/api/v1/clinic/charts/<id>/versions \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "165a182b-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "chart_id": "b82b40b8-0000-4000-8000-000000000001",
        "version": 1,
        "state": {
          "note": "Resto de las piezas sin hallazgos",
          "entries": {
            "26": {
              "items": [
                {
                  "code": "caries",
                  "note": null,
                  "unit": null,
                  "label": "Caries oclusal",
                  "quantity": null,
                  "surfaces": [
                    "O"
                  ]
                }
              ]
            }
          }
        },
        "notation": "fdi",
        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
        "author_user_id": "11111111-0000-4000-8000-000000000001",
        "author_label": "[email protected]",
        "summary": "Odontograma inicial",
        "created_at": "2026-09-22T22:21:17.539Z"
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 50,
    "chart": {
      "id": "b82b40b8-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "kind": "odontograma",
      "notation": "fdi",
      "current_version": 1,
      "current_version_id": "165a182b-0000-4000-8000-000000000001",
      "created_at": "2026-09-22T22:21:17.537Z",
      "updated_at": "2026-09-22T22:21:17.539Z"
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/charts/{chartId}/versions/{version}

One past state of a chart

The state as it was at that version — what the diff and the "ver esta versión" control read. Immutable: this row is what somebody signed.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
chartIdpathuuidyes
versionpathintegeryes≥ 1
curl https://api.vitrinadev.com/api/v1/clinic/charts/<id>/versions/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "165a182b-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
    "version": 1,
    "state": {
      "note": "Resto de las piezas sin hallazgos",
      "entries": {
        "26": {
          "items": [
            {
              "code": "caries",
              "note": null,
              "unit": null,
              "label": "Caries oclusal",
              "quantity": null,
              "surfaces": [
                "O"
              ]
            }
          ]
        }
      }
    },
    "notation": "fdi",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "author_user_id": "11111111-0000-4000-8000-000000000001",
    "author_label": "[email protected]",
    "summary": "Odontograma inicial",
    "created_at": "2026-09-22T22:21:17.539Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/encounters/{id}

One atención with its evoluciones

Requires clinic_record:read AND the PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/encounters/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "434a1a10-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "treatment_plan_id": null,
    "form_template_id": null,
    "kind": "control",
    "status": "open",
    "starts_at": "2026-09-22T22:21:17.396Z",
    "ends_at": null,
    "closed_at": null,
    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
    "closed_by_user_id": null,
    "voided_reason": null,
    "author": {
      "kind": "member",
      "id": "11111111-0000-4000-8000-000000000001",
      "name": null,
      "via": {
        "kind": "personal_token",
        "name": "Token personal de la dirección"
      }
    },
    "created_at": "2026-09-22T22:21:17.394Z",
    "updated_at": "2026-09-22T22:21:17.394Z",
    "notes": []
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/encounters/{id}/close

Close an atención (idempotent)

Closing a closed atención returns it unchanged rather than re-stamping closed_at — somebody may already have quoted that time. A voided atención cannot be closed. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/encounters/<id>/close \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "434a1a10-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "treatment_plan_id": null,
    "form_template_id": null,
    "kind": "control",
    "status": "closed",
    "starts_at": "2026-09-22T22:21:17.396Z",
    "ends_at": null,
    "closed_at": "2026-09-22T22:21:17.465Z",
    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
    "closed_by_user_id": "11111111-0000-4000-8000-000000000001",
    "voided_reason": null,
    "author": {
      "kind": "member",
      "id": "11111111-0000-4000-8000-000000000001",
      "name": null,
      "via": {
        "kind": "personal_token",
        "name": "Token personal de la dirección"
      }
    },
    "created_at": "2026-09-22T22:21:17.394Z",
    "updated_at": "2026-09-22T22:21:17.464Z",
    "notes": [
      {
        "id": "c51048b7-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "author_user_id": "11111111-0000-4000-8000-000000000001",
        "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
        "kind": "evolucion",
        "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido. Próximo control en 30 días.",
        "structured": {},
        "signed_at": "2026-09-22T22:21:17.444Z",
        "signature_meta": {
          "signed_by": "11111111-0000-4000-8000-000000000001",
          "request_id": "088e29b0-0000-4000-8000-000000000004",
          "signed_by_kind": "user",
          "signed_by_label": "[email protected]"
        },
        "amends_note_id": null,
        "amend_reason": null,
        "author": {
          "kind": "member",
          "id": "11111111-0000-4000-8000-000000000001",
          "name": null,
          "via": {
            "kind": "personal_token",
            "name": "Token personal de la dirección"
          }
        },
        "created_at": "2026-09-22T22:21:17.420Z",
        "amended_note": null
      },
      {
        "id": "cc352edb-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "author_user_id": "11111111-0000-4000-8000-000000000001",
        "author_professional_id": null,
        "kind": "evolucion",
        "body": "Control de ortodoncia. Se ajusta arco superior e inferior; sin dolor referido.",
        "structured": {},
        "signed_at": "2026-09-22T22:21:17.454Z",
        "signature_meta": {
          "signed_by": "11111111-0000-4000-8000-000000000001",
          "request_id": "088e29b0-0000-4000-8000-000000000005",
          "signed_by_kind": "user",
          "signed_by_label": "[email protected]"
        },
        "amends_note_id": "c51048b7-0000-4000-8000-000000000001",
        "amend_reason": "Se omitió el arco inferior en la evolución firmada",
        "author": {
          "kind": "member",
          "id": "11111111-0000-4000-8000-000000000001",
          "name": null,
          "via": {
            "kind": "personal_token",
            "name": "Token personal de la dirección"
          }
        },
        "created_at": "2026-09-22T22:21:17.453Z",
        "amended_note": {
          "id": "c51048b7-0000-4000-8000-000000000001",
          "created_at": "2026-09-22T22:21:17.420Z"
        }
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/encounters/{id}/notes

Evoluciones of one atención, oldest first

The order the amendment chain is read in. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/encounters/<id>/notes \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "c51048b7-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "author_user_id": "11111111-0000-4000-8000-000000000001",
        "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
        "kind": "evolucion",
        "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido.",
        "structured": {},
        "signed_at": null,
        "signature_meta": null,
        "amends_note_id": null,
        "amend_reason": null,
        "author": {
          "kind": "member",
          "id": "11111111-0000-4000-8000-000000000001",
          "name": null,
          "via": {
            "kind": "personal_token",
            "name": "Token personal de la dirección"
          }
        },
        "created_at": "2026-09-22T22:21:17.420Z",
        "amended_note": null
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/encounters/{id}/notes

Write an evolución

Authored by a NAMED WRITER — an agent may never write a clinical note (ADR 0083 §8); it records what a patient reports as an antecedente instead. sign: true signs it as written, after which the row is IMMUTABLE and the only correction path is /notes/\{id\}/amend. The writer is named honestly: author_user_id is the MEMBER who wrote it and is null for an API key, and author carries what that column cannot — the key with the name it had at that moment, or the member plus the connected app / personal token they acted through (ADR 0106 §3.1). A connected app never reaches this route at all. performed_plan_item_ids (with sign: true and the clinic_money:write scope) marks those pending treatment-plan lines of this patient «realizada» in the same transaction and freezes what was performed in structured.performed_plan_items; a line of another patient is a 400 and a line that is no longer pending is a 409. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
bodystringyesmín. 1, máx. 20000
kindanamnesis \evolucion \indicaciones \
structuredobject—
author_professional_idstring | null—
signboolean—
performed_plan_item_idsuuid[]—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/encounters/<id>/notes \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "evolucion",
    "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido.",
    "author_professional_id": "18342d1b-0000-4000-8000-000000000001"
  }'

Example response (201)

{
  "data": {
    "id": "c51048b7-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "author_user_id": "11111111-0000-4000-8000-000000000001",
    "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
    "kind": "evolucion",
    "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido.",
    "structured": {},
    "signed_at": null,
    "signature_meta": null,
    "amends_note_id": null,
    "amend_reason": null,
    "author": {
      "kind": "member",
      "id": "11111111-0000-4000-8000-000000000001",
      "name": null,
      "via": {
        "kind": "personal_token",
        "name": "Token personal de la dirección"
      }
    },
    "created_at": "2026-09-22T22:21:17.420Z",
    "amended_note": null
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

PUT /clinic/encounters/{id}/sterilization-lote

Record the sterilised-pack lote used on an atención

Sets, changes or clears (lote: null) the optional lote of the sterilised instrument pack used on this atención (#4004). Free text: it is matched to an Esterilización cycle by value, not by id, so a lote logged on paper or in Dentalink can still be recorded. Refused on a voided or locked atención. The lote is not copied into the audit row. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
lotestring | nullyesmáx. 60
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/encounters/<id>/sterilization-lote \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lote": "A-0412"
  }'

Example response (200)

{
  "data": {
    "id": "e0e57121-0000-4000-8000-0000000006ab",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "appointment_id": null,
    "professional_id": null,
    "location_id": null,
    "treatment_plan_id": null,
    "form_template_id": null,
    "sterilization_lote": "A-0412",
    "kind": "procedimiento",
    "status": "open",
    "starts_at": "2026-10-09T18:00:00.000Z",
    "ends_at": null,
    "closed_at": null,
    "opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
    "closed_by_user_id": null,
    "voided_reason": null,
    "author": null,
    "created_at": "2026-10-09T18:00:00.000Z",
    "updated_at": "2026-10-09T18:05:00.000Z",
    "notes": []
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/encounters/{id}/void

Void an atención (idempotent)

Voids an atención opened or closed in error; the row stays, with its reason. Voiding an already voided atención returns it unchanged. Refused on a locked atención. The cita lines that closing performed go back to not performed, so their insumos return to stock. The reason is not copied into the audit row. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
reasonstringyesmín. 1, máx. 500
curl -X POST https://api.vitrinadev.com/api/v1/clinic/encounters/<id>/void \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Abierta por error"
  }'

Example response (200)

{
  "data": {
    "id": "e0e57121-0000-4000-8000-0000000007ab",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
    "professional_id": null,
    "location_id": null,
    "treatment_plan_id": null,
    "form_template_id": null,
    "sterilization_lote": null,
    "kind": "control",
    "status": "voided",
    "starts_at": "2026-10-09T18:00:00.000Z",
    "ends_at": null,
    "closed_at": "2026-10-09T18:40:00.000Z",
    "opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
    "closed_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
    "voided_reason": "Abierta por error",
    "author": null,
    "created_at": "2026-10-09T18:00:00.000Z",
    "updated_at": "2026-10-09T18:45:00.000Z",
    "notes": []
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/flags/{id}/resolve

Lift a patient alert

The row stays — who raised it and why is part of the record. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
reasonstring | null—máx. 500
curl -X POST https://api.vitrinadev.com/api/v1/clinic/flags/<id>/resolve \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Descartada por el alergólogo"
  }'

Example response (200)

{
  "data": {
    "id": "bb4c2278-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "kind": "alergia",
    "label": "Alergia a penicilina",
    "detail": null,
    "severity": "severa",
    "shown_on_agenda": true,
    "history_item_id": null,
    "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
    "recorded_source": "staff",
    "resolved_at": "2026-09-22T22:21:17.527Z",
    "resolved_by_user_id": "11111111-0000-4000-8000-000000000001",
    "resolved_reason": "Descartada por el alergólogo",
    "created_at": "2026-09-22T22:21:17.510Z",
    "updated_at": "2026-09-22T22:21:17.526Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/forms/{id}

One filled ficha, with the questions it was answered against

Requires clinic_record:read AND the PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/forms/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "d00b3981-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "template_id": "3226ecbe-0000-4000-8000-000000000002",
    "template_version_id": "c692273d-0000-4000-8000-000000000003",
    "template_version": 1,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "appointment_id": null,
    "values": {},
    "filled_by": "staff",
    "status": "pending",
    "submitted_at": null,
    "submitted_by_user_id": null,
    "submitted_by_contact_id": null,
    "submitted_ip": null,
    "submitted_user_agent": null,
    "amends_response_id": null,
    "amend_reason": null,
    "voided_reason": null,
    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-22T22:21:18.106Z",
    "updated_at": "2026-09-22T22:21:18.106Z",
    "template_name": "Ficha de ingreso",
    "schema": {
      "sections": [
        {
          "key": "salud",
          "title": "Antecedentes de salud",
          "fields": [
            {
              "key": "alergias",
              "type": "text",
              "label": "¿Tiene alergias?",
              "required": true
            },
            {
              "key": "medicamentos",
              "type": "text",
              "label": "¿Toma algún medicamento?"
            }
          ]
        }
      ]
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

PATCH /clinic/forms/{id}

Save answers into an unfinished ficha

Only while pending or draft. A SUBMITTED ficha is refused here — the correction path is /forms/\{id\}/amend, and the repo, the service and a database trigger each refuse the in-place edit. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
valuesobjectyes
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/forms/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "values": {
      "alergias": "Penicilina"
    }
  }'

Example response (200)

{
  "data": {
    "id": "d00b3981-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "template_id": "3226ecbe-0000-4000-8000-000000000002",
    "template_version_id": "c692273d-0000-4000-8000-000000000003",
    "template_version": 1,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "appointment_id": null,
    "values": {
      "alergias": "Penicilina"
    },
    "filled_by": "staff",
    "status": "draft",
    "submitted_at": null,
    "submitted_by_user_id": null,
    "submitted_by_contact_id": null,
    "submitted_ip": null,
    "submitted_user_agent": null,
    "amends_response_id": null,
    "amend_reason": null,
    "voided_reason": null,
    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-22T22:21:18.106Z",
    "updated_at": "2026-09-22T22:21:18.165Z",
    "template_name": "Ficha de ingreso",
    "schema": {
      "sections": [
        {
          "key": "salud",
          "title": "Antecedentes de salud",
          "fields": [
            {
              "key": "alergias",
              "type": "text",
              "label": "¿Tiene alergias?",
              "required": true
            },
            {
              "key": "medicamentos",
              "type": "text",
              "label": "¿Toma algún medicamento?"
            }
          ]
        }
      ]
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/forms/{id}/amend

Amend a submitted ficha (a NEW response, with a reason)

Pinned to the SAME version, pointing at the predecessor, which becomes amended and keeps every answer it had. One successor per response: the correction history is a chain, not a tree. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
valuesobjectyes
reasonstringyesmín. 3, máx. 600
curl -X POST https://api.vitrinadev.com/api/v1/clinic/forms/<id>/amend \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "values": {
      "alergias": "Penicilina y AINEs",
      "medicamentos": "Ninguno"
    },
    "reason": "La paciente agrega una alergia al revisar la ficha"
  }'

Example response (201)

{
  "data": {
    "id": "cc352edb-0000-4000-8000-000000000002",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "template_id": "3226ecbe-0000-4000-8000-000000000002",
    "template_version_id": "c692273d-0000-4000-8000-000000000003",
    "template_version": 1,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "appointment_id": null,
    "values": {
      "alergias": "Penicilina y AINEs",
      "medicamentos": "Ninguno"
    },
    "filled_by": "staff",
    "status": "submitted",
    "submitted_at": "2026-09-22T22:21:18.190Z",
    "submitted_by_user_id": "11111111-0000-4000-8000-000000000001",
    "submitted_by_contact_id": null,
    "submitted_ip": null,
    "submitted_user_agent": null,
    "amends_response_id": "d00b3981-0000-4000-8000-000000000001",
    "amend_reason": "La paciente agrega una alergia al revisar la ficha",
    "voided_reason": null,
    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-22T22:21:18.188Z",
    "updated_at": "2026-09-22T22:21:18.189Z",
    "template_name": "Ficha de ingreso",
    "schema": {
      "sections": [
        {
          "key": "salud",
          "title": "Antecedentes de salud",
          "fields": [
            {
              "key": "alergias",
              "type": "text",
              "label": "¿Tiene alergias?",
              "required": true
            },
            {
              "key": "medicamentos",
              "type": "text",
              "label": "¿Toma algún medicamento?"
            }
          ]
        }
      ]
    }
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/forms/{id}/link

Mint the tokenised link the patient fills on their phone

Refused for a template that is not patient_fillable: a ficha written for a professional to complete in front of a patient is not a ficha to hand the patient. The link is an HS256 capability token; the ROW, not the token, decides whether it is still fillable. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/forms/<id>/link \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "response_id": "d00b3981-0000-4000-8000-000000000001",
    "url": "http://localhost:3000/ficha/<token>",
    "expires_in_seconds": 1209600
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/forms/{id}/submit

Submit a ficha (the last write that ever touches its answers)

Required answers are validated against the PINNED version, never the live one: a template that gained a required question yesterday must not make a link sent last week unanswerable. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
valuesobjectyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/forms/<id>/submit \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "values": {
      "alergias": "Penicilina",
      "medicamentos": "Ninguno"
    }
  }'

Example response (200)

{
  "data": {
    "id": "d00b3981-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "template_id": "3226ecbe-0000-4000-8000-000000000002",
    "template_version_id": "c692273d-0000-4000-8000-000000000003",
    "template_version": 1,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "appointment_id": null,
    "values": {
      "alergias": "Penicilina",
      "medicamentos": "Ninguno"
    },
    "filled_by": "staff",
    "status": "submitted",
    "submitted_at": "2026-09-22T22:21:18.177Z",
    "submitted_by_user_id": null,
    "submitted_by_contact_id": null,
    "submitted_ip": null,
    "submitted_user_agent": null,
    "amends_response_id": null,
    "amend_reason": null,
    "voided_reason": null,
    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-22T22:21:18.106Z",
    "updated_at": "2026-09-22T22:21:18.176Z",
    "template_name": "Ficha de ingreso",
    "schema": {
      "sections": [
        {
          "key": "salud",
          "title": "Antecedentes de salud",
          "fields": [
            {
              "key": "alergias",
              "type": "text",
              "label": "¿Tiene alergias?",
              "required": true
            },
            {
              "key": "medicamentos",
              "type": "text",
              "label": "¿Toma algún medicamento?"
            }
          ]
        }
      ]
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/forms/pending

«Fichas pendientes» — intake forms owed before an upcoming cita

A prestación with requires_form_template_id owes a SUBMITTED response before its cita. A response that exists as pending or draft is still owed, and is exactly the row «reenviar enlace» acts on. A worklist read across patients: it carries a name and what is missing, never an answer, so it gates on clinic_record:read and opening a row is the disclosure.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
fromquerystringnodate-time
toquerystringnodate-time
limitqueryintegerno≥ 1, ≤ 500, por defecto 200
curl https://api.vitrinadev.com/api/v1/clinic/forms/pending \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [],
    "total": 0
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/forms/requirements

Is this cita confirmable? What is still missing

Returns the LIST of missing requirements (forms and consents) rather than a boolean, because a refusal has to name what is missing: «falta la ficha de ingreso» is actionable, «requisitos incompletos» is not. Empty array = confirmable. Requires clinic_record:read.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
clinic_patient_idqueryuuidyes
service_idsquerystringyesmín. 1
curl https://api.vitrinadev.com/api/v1/clinic/forms/requirements \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [],
    "confirmable": true
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

PATCH /clinic/notes/{id}

Correct an UNSIGNED evolución in place

Drafts only. A SIGNED note is immutable and this endpoint 400s pointing at /notes/\{id\}/amend — the repo refuses the update and so does a database trigger. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
bodystring—mín. 1, máx. 20000
kindanamnesis \evolucion \indicaciones \
structuredobject—
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/notes/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido. Próximo control en 30 días."
  }'

Example response (200)

{
  "data": {
    "id": "c51048b7-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "author_user_id": "11111111-0000-4000-8000-000000000001",
    "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
    "kind": "evolucion",
    "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido. Próximo control en 30 días.",
    "structured": {},
    "signed_at": null,
    "signature_meta": null,
    "amends_note_id": null,
    "amend_reason": null,
    "author": {
      "kind": "member",
      "id": "11111111-0000-4000-8000-000000000001",
      "name": null,
      "via": {
        "kind": "personal_token",
        "name": "Token personal de la dirección"
      }
    },
    "created_at": "2026-09-22T22:21:17.420Z",
    "amended_note": null
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/notes/{id}/amend

Amend a signed evolución (a NEW row, never an edit)

The regulatory shape (Ley 20.584 / Ley 21.719): the successor is born signed, carries a MANDATORY reason and points at the note it corrects. One successor per note, so the history is a chain and not a tree of rival versions. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
bodystringyesmín. 1, máx. 20000
reasonstringyesmín. 3, máx. 500
structuredobject—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/notes/<id>/amend \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Control de ortodoncia. Se ajusta arco superior e inferior; sin dolor referido.",
    "reason": "Se omitió el arco inferior en la evolución firmada"
  }'

Example response (201)

{
  "data": {
    "id": "cc352edb-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "author_user_id": "11111111-0000-4000-8000-000000000001",
    "author_professional_id": null,
    "kind": "evolucion",
    "body": "Control de ortodoncia. Se ajusta arco superior e inferior; sin dolor referido.",
    "structured": {},
    "signed_at": "2026-09-22T22:21:17.454Z",
    "signature_meta": {
      "signed_by": "11111111-0000-4000-8000-000000000001",
      "request_id": "088e29b0-0000-4000-8000-000000000005",
      "signed_by_kind": "user",
      "signed_by_label": "[email protected]"
    },
    "amends_note_id": "c51048b7-0000-4000-8000-000000000001",
    "amend_reason": "Se omitió el arco inferior en la evolución firmada",
    "author": {
      "kind": "member",
      "id": "11111111-0000-4000-8000-000000000001",
      "name": null,
      "via": {
        "kind": "personal_token",
        "name": "Token personal de la dirección"
      }
    },
    "created_at": "2026-09-22T22:21:17.453Z",
    "amended_note": {
      "id": "c51048b7-0000-4000-8000-000000000001",
      "created_at": "2026-09-22T22:21:17.420Z"
    }
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/notes/{id}/sign

Sign an evolución

Locks the row forever. Signing an already-signed note 400s rather than re-stamping the signature: the second signer is a person who believes they just signed something. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/notes/<id>/sign \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "c51048b7-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "author_user_id": "11111111-0000-4000-8000-000000000001",
    "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
    "kind": "evolucion",
    "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido. Próximo control en 30 días.",
    "structured": {},
    "signed_at": "2026-09-22T22:21:17.444Z",
    "signature_meta": {
      "signed_by": "11111111-0000-4000-8000-000000000001",
      "request_id": "088e29b0-0000-4000-8000-000000000004",
      "signed_by_kind": "user",
      "signed_by_label": "[email protected]"
    },
    "amends_note_id": null,
    "amend_reason": null,
    "author": {
      "kind": "member",
      "id": "11111111-0000-4000-8000-000000000001",
      "name": null,
      "via": {
        "kind": "personal_token",
        "name": "Token personal de la dirección"
      }
    },
    "created_at": "2026-09-22T22:21:17.420Z",
    "amended_note": null
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/antecedentes

Antecedentes and patient alerts

The «Ficha clínica» tab: live antecedentes (mórbidos, quirúrgicos, alérgicos, medicamentos, hábitos, familiares) AND live alerts, in one answer — one tab is one disclosure and therefore one access-log row. include_superseded=true adds the corrected rows. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
include_supersededquerytrue \falseno
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/antecedentes \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "antecedentes": [
      {
        "id": "02db347c-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "kind": "alergia",
        "label": "Alergia a penicilina",
        "detail": "Referida por la paciente; reacción cutánea",
        "severity": "severa",
        "status": "active",
        "onset_on": null,
        "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
        "recorded_source": "staff",
        "superseded_at": null,
        "superseded_by": null,
        "superseded_reason": null,
        "created_at": "2026-09-22T22:21:17.476Z"
      }
    ],
    "flags": []
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/antecedentes

Record an antecedente

Append-only. Correcting one is /antecedentes/\{id\}/supersede, never an edit — what the record said when a decision was taken has to stay readable. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
kindalergia \patologia \medicamento \
labelstringyesmín. 1, máx. 200
detailstring | null—máx. 2000
severityleve \moderada \severa \
statusactive \resolved—
onset_onstring | null—patrón ^\d{4}-\d{2}-\d{2}$
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/antecedentes \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "alergia",
    "label": "Alergia a penicilina",
    "detail": "Referida por la paciente; reacción cutánea",
    "severity": "severa"
  }'

Example response (201)

{
  "data": {
    "id": "02db347c-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "kind": "alergia",
    "label": "Alergia a penicilina",
    "detail": "Referida por la paciente; reacción cutánea",
    "severity": "severa",
    "status": "active",
    "onset_on": null,
    "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
    "recorded_source": "staff",
    "superseded_at": null,
    "superseded_by": null,
    "superseded_reason": null,
    "created_at": "2026-09-22T22:21:17.476Z"
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/chart-findings

Everything found on this patient, across every chart

The findings of all this patient’s charts, newest first, optionally filtered to active or treated. What a presupuesto screen asks for: each row carries tooth_or_zone, the surfaces for a tooth, and the treatment_plan_item_id it already produced (null = not quoted yet).

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
statusqueryactive \treatedno
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/chart-findings \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "e29e8d94-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "chart_id": "b82b40b8-0000-4000-8000-000000000001",
        "tooth_or_zone": "26",
        "surfaces": [
          "O"
        ],
        "code": "caries",
        "label": "Caries oclusal",
        "note": null,
        "status": "active",
        "first_seen_version": 1,
        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
        "treatment_plan_item_id": null,
        "clinic_appointment_service_id": null,
        "created_by_user_id": "11111111-0000-4000-8000-000000000001",
        "treated_at": null,
        "treated_by_user_id": null,
        "created_at": "2026-09-22T22:21:17.577Z",
        "updated_at": "2026-09-22T22:21:17.577Z"
      }
    ],
    "total": 1
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/charts

The specialty charts a patient has

Every chart of one patient — odontograma, periodontograma, mapa facial, mapa corporal — with its notation and the version it is currently on. No state: the picture comes from the per-kind endpoint below.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/charts \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "b82b40b8-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "kind": "odontograma",
        "notation": "fdi",
        "current_version": 1,
        "current_version_id": "165a182b-0000-4000-8000-000000000001",
        "created_at": "2026-09-22T22:21:17.537Z",
        "updated_at": "2026-09-22T22:21:17.539Z"
      }
    ],
    "total": 1
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/charts/{kind}

One chart: its current state, its findings and its vocabulary

The chart as it stands, plus the findings painted on it and the vocabulary the client should render (notation, the notations this kind allows, and the zone list for a face/body map). Creates the chart row on first look — current: null then means "never filled in", which is NOT the same as "every tooth is healthy" and must not be rendered as an empty chart. A version carried across from the clinic's previous system by an instructed migration run has imported_from set (healthatom) and imported_as_of = the date that system showed («Importado de Dentalink · <fecha>»); it is read-only like every version, and a save draws the next version on top of it.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
kindpathodontograma \periodontograma \mapa_facial \
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/charts/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "chart": {
      "id": "b82b40b8-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "kind": "odontograma",
      "notation": "fdi",
      "current_version": 1,
      "current_version_id": "165a182b-0000-4000-8000-000000000001",
      "created_at": "2026-09-22T22:21:17.537Z",
      "updated_at": "2026-09-22T22:21:17.539Z"
    },
    "current": {
      "id": "165a182b-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "chart_id": "b82b40b8-0000-4000-8000-000000000001",
      "version": 1,
      "state": {
        "note": "Resto de las piezas sin hallazgos",
        "entries": {
          "26": {
            "items": [
              {
                "code": "caries",
                "note": null,
                "unit": null,
                "label": "Caries oclusal",
                "quantity": null,
                "surfaces": [
                  "O"
                ]
              }
            ]
          }
        }
      },
      "notation": "fdi",
      "encounter_id": "434a1a10-0000-4000-8000-000000000001",
      "author_user_id": "11111111-0000-4000-8000-000000000001",
      "author_label": "[email protected]",
      "summary": "Odontograma inicial",
      "created_at": "2026-09-22T22:21:17.539Z"
    },
    "findings": [],
    "vocabulary": {
      "notation": "fdi",
      "allowed_notations": [
        "fdi",
        "palmer",
        "universal"
      ],
      "zones": []
    }
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/charts/{kind}/versions

Save the chart — an edit creates a version

Saves the WHOLE state as a new immutable version (the database refuses an update of a saved one) and records any findings the same edit declared, in one transaction. expected_version is an optimistic lock: pass the version the editor had on screen and a save made after somebody else’s is refused with a 409 instead of overwriting it. Keys are validated against the chart’s own notation — FDI tooth numbers, Palmer, Universal, or the zone vocabulary of a mapa facial / corporal — never against a global one.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
kindpathodontograma \periodontograma \mapa_facial \

Body

FieldTypeRequiredConstraints
stateobjectyes
notationfdi \palmer \universal \
encounter_idstring | null—
summarystring | null—máx. 400
expected_versioninteger | null—≥ 0
findingsobject[]—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/charts/<id>/versions \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notation": "fdi",
    "summary": "Odontograma inicial",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "state": {
      "entries": {
        "26": {
          "items": [
            {
              "code": "caries",
              "surfaces": [
                "O"
              ],
              "label": "Caries oclusal"
            }
          ]
        }
      },
      "note": "Resto de las piezas sin hallazgos"
    }
  }'

Example response (201)

{
  "data": {
    "chart": {
      "id": "b82b40b8-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
      "kind": "odontograma",
      "notation": "fdi",
      "current_version": 1,
      "current_version_id": "165a182b-0000-4000-8000-000000000001",
      "created_at": "2026-09-22T22:21:17.537Z",
      "updated_at": "2026-09-22T22:21:17.539Z"
    },
    "version": {
      "id": "165a182b-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "chart_id": "b82b40b8-0000-4000-8000-000000000001",
      "version": 1,
      "state": {
        "note": "Resto de las piezas sin hallazgos",
        "entries": {
          "26": {
            "items": [
              {
                "code": "caries",
                "note": null,
                "unit": null,
                "label": "Caries oclusal",
                "quantity": null,
                "surfaces": [
                  "O"
                ]
              }
            ]
          }
        }
      },
      "notation": "fdi",
      "encounter_id": "434a1a10-0000-4000-8000-000000000001",
      "author_user_id": "11111111-0000-4000-8000-000000000001",
      "author_label": "[email protected]",
      "summary": "Odontograma inicial",
      "created_at": "2026-09-22T22:21:17.539Z"
    },
    "findings": []
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/encounters

Atenciones of one patient, newest first, with their notes

The «Atenciones» tab. Each atención carries its evoluciones in order, including the amendment chain: an amended note is a NEW row pointing at the one it corrects via amends_note_id, and both stay. Voided atenciones are hidden unless include_voided=true. Requires clinic_record:read AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 1, ≤ 100, por defecto 25
include_voidedquerytrue \falseno
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/encounters \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "434a1a10-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
        "professional_id": "18342d1b-0000-4000-8000-000000000001",
        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
        "treatment_plan_id": null,
        "form_template_id": null,
        "kind": "control",
        "status": "open",
        "starts_at": "2026-09-22T22:21:17.396Z",
        "ends_at": null,
        "closed_at": null,
        "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
        "closed_by_user_id": null,
        "voided_reason": null,
        "author": {
          "kind": "member",
          "id": "11111111-0000-4000-8000-000000000001",
          "name": null,
          "via": {
            "kind": "personal_token",
            "name": "Token personal de la dirección"
          }
        },
        "created_at": "2026-09-22T22:21:17.394Z",
        "updated_at": "2026-09-22T22:21:17.394Z",
        "notes": []
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 25
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/encounters

Open an atención

IDEMPOTENT on appointment_id: an attended cita opens exactly one live atención, so the arrival rail, the ficha and a sweep can all call this without minting rivals. form_template_id is Reservo's "tipo de ficha" (the versioned form this atención is recorded on). opened_by_user_id is the MEMBER who opened it and is null for an API key; author then says which key it was (ADR 0106 §3.1). Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
appointment_idstring | null—
professional_idstring | null—
location_idstring | null—
form_template_idstring | null—
treatment_plan_idstring | null—
sterilization_lotestring | null—máx. 60
kindconsulta \sesion \control \
starts_atstring | null—date-time
ends_atstring | null—date-time
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/encounters \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "kind": "control"
  }'

Example response (201)

{
  "data": {
    "id": "434a1a10-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
    "professional_id": "18342d1b-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "treatment_plan_id": null,
    "form_template_id": null,
    "kind": "control",
    "status": "open",
    "starts_at": "2026-09-22T22:21:17.396Z",
    "ends_at": null,
    "closed_at": null,
    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
    "closed_by_user_id": null,
    "voided_reason": null,
    "author": {
      "kind": "member",
      "id": "11111111-0000-4000-8000-000000000001",
      "name": null,
      "via": {
        "kind": "personal_token",
        "name": "Token personal de la dirección"
      }
    },
    "created_at": "2026-09-22T22:21:17.394Z",
    "updated_at": "2026-09-22T22:21:17.394Z",
    "notes": []
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/flags

Raise a patient alert

alergia · riesgo · vip · no_agendar_sin_abono · cuidado_especial · custom. THERE IS NO deudor KIND: what a patient owes is the payments ledger's answer, it changes with every payment, and a stale red debt banner on a clinical record is both wrong and humiliating. shown_on_agenda broadcasts the label to everyone who can read the diary. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
kindalergia \riesgo \vip \
labelstringyesmín. 1, máx. 200
detailstring | null—máx. 2000
severityleve \moderada \severa \
shown_on_agendaboolean—
history_item_idstring | null—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/flags \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "alergia",
    "label": "Alergia a penicilina",
    "severity": "severa",
    "shown_on_agenda": true
  }'

Example response (201)

{
  "data": {
    "id": "bb4c2278-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "kind": "alergia",
    "label": "Alergia a penicilina",
    "detail": null,
    "severity": "severa",
    "shown_on_agenda": true,
    "history_item_id": null,
    "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
    "recorded_source": "staff",
    "resolved_at": null,
    "resolved_by_user_id": null,
    "resolved_reason": null,
    "created_at": "2026-09-22T22:21:17.510Z",
    "updated_at": "2026-09-22T22:21:17.510Z"
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/forms

Fichas filled for one patient, newest first

Each response carries the SCHEMA IT WAS ANSWERED AGAINST, not the live one — that is the version pin. Requires clinic_record:read AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
encounter_idqueryuuidno
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/forms \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "d00b3981-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "template_id": "3226ecbe-0000-4000-8000-000000000002",
        "template_version_id": "c692273d-0000-4000-8000-000000000003",
        "template_version": 1,
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
        "appointment_id": null,
        "values": {},
        "filled_by": "staff",
        "status": "pending",
        "submitted_at": null,
        "submitted_by_user_id": null,
        "submitted_by_contact_id": null,
        "submitted_ip": null,
        "submitted_user_agent": null,
        "amends_response_id": null,
        "amend_reason": null,
        "voided_reason": null,
        "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
        "created_at": "2026-09-22T22:21:18.106Z",
        "updated_at": "2026-09-22T22:21:18.106Z",
        "template_name": "Ficha de ingreso",
        "schema": {
          "sections": [
            {
              "key": "salud",
              "title": "Antecedentes de salud",
              "fields": [
                {
                  "key": "alergias",
                  "type": "text",
                  "label": "¿Tiene alergias?",
                  "required": true
                },
                {
                  "key": "medicamentos",
                  "type": "text",
                  "label": "¿Toma algún medicamento?"
                }
              ]
            }
          ]
        }
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 50
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/forms

Open a ficha against the template’s live version

The version is resolved ONCE, here, and stored on the row; nothing downstream consults current_version again. as_request: true mints it as pending — a link to send rather than a form being typed. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
template_iduuidyes
encounter_idstring | null—
appointment_idstring | null—
valuesobject—
as_requestboolean—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/forms \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "3226ecbe-0000-4000-8000-000000000002",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "as_request": true
  }'

Example response (201)

{
  "data": {
    "id": "d00b3981-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "template_id": "3226ecbe-0000-4000-8000-000000000002",
    "template_version_id": "c692273d-0000-4000-8000-000000000003",
    "template_version": 1,
    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
    "appointment_id": null,
    "values": {},
    "filled_by": "staff",
    "status": "pending",
    "submitted_at": null,
    "submitted_by_user_id": null,
    "submitted_by_contact_id": null,
    "submitted_ip": null,
    "submitted_user_agent": null,
    "amends_response_id": null,
    "amend_reason": null,
    "voided_reason": null,
    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
    "created_at": "2026-09-22T22:21:18.106Z",
    "updated_at": "2026-09-22T22:21:18.106Z",
    "template_name": "Ficha de ingreso",
    "schema": {
      "sections": [
        {
          "key": "salud",
          "title": "Antecedentes de salud",
          "fields": [
            {
              "key": "alergias",
              "type": "text",
              "label": "¿Tiene alergias?",
              "required": true
            },
            {
              "key": "medicamentos",
              "type": "text",
              "label": "¿Toma algún medicamento?"
            }
          ]
        }
      ]
    }
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/history

A patient's clinical history (Reservo)

The vendor's atenciones timeline — recetas and fichas clínicas, newest first, each with the record's stripped text — read LIVE off the Reservo session (never mirrored). Reservo-backed patients only: other sources answer supported: false with no entries.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/history \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "supported": false,
    "entries": []
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/prescriptions

The recetas on one patient’s ficha

data holds the native recetas, newest first, each with its items, its status (draft | signed), last_send (the newest delivered send of a signed receta — channel, a masked recipient_hint, sent_at, sent_by — or null) and — when a newer receta corrected it — replaced_by. documents holds the patient’s OTHER recetas: clinic documents of kind receta that no native receta owns (imported from the previous system, or uploaded by hand), so both kinds read together in one disclosure. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/prescriptions \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "01929b7e-0000-7000-8000-000000000101",
        "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
        "encounter_id": null,
        "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
        "kind": "simple",
        "notes": "Tomar con alimentos.",
        "replaces_prescription_id": null,
        "replace_reason": null,
        "status": "signed",
        "signed_at": "2026-11-02T13:42:00.000Z",
        "prescriber": {
          "id": "01929b7e-0000-7000-8000-000000000301",
          "nombre": "Profesional Ejemplo",
          "especialidad": "Endodoncia"
        },
        "document_id": "01929b7e-0000-7000-8000-000000000401",
        "replaced_by": null,
        "items": [
          {
            "id": "01929b7e-0000-7000-8000-000000000501",
            "position": 1,
            "medication": "Amoxicilina 500 mg",
            "dose": "1 comprimido",
            "instructions": "Cada 8 horas durante 7 días."
          }
        ]
      }
    ],
    "documents": []
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/patients/{id}/prescriptions

Write a receta (and optionally sign it)

Free-text items (medication, dose, instructions; 1–20), the prescribing professional (required), an optional atención and general notes. Born a draft unless sign: true, which signs it in the same call — the ordinary path for a dentist. Only a member signs: an API key may prepare a draft and is refused sign: true. To CORRECT a signed receta, write a new one with replaces_prescription_id and a mandatory replace_reason; a receta is replaced at most once. Controlled and retained recetas are issued outside Vitrina. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
prescriber_professional_iduuidyes
encounter_idstring | null—
notesstring | null—máx. 2000
itemsobject[]yes
replaces_prescription_idstring | null—
replace_reasonstring | null—máx. 500
signboolean—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/prescriptions \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
    "notes": "Tomar con alimentos.",
    "items": [
      {
        "medication": "Amoxicilina 500 mg",
        "dose": "1 comprimido",
        "instructions": "Cada 8 horas durante 7 días."
      }
    ],
    "sign": true
  }'

Example response (201)

{
  "data": {
    "id": "01929b7e-0000-7000-8000-000000000101",
    "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
    "encounter_id": null,
    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
    "kind": "simple",
    "notes": "Tomar con alimentos.",
    "replaces_prescription_id": null,
    "replace_reason": null,
    "status": "signed",
    "signed_at": "2026-11-02T13:42:00.000Z",
    "prescriber": {
      "id": "01929b7e-0000-7000-8000-000000000301",
      "nombre": "Profesional Ejemplo",
      "especialidad": "Endodoncia"
    },
    "document_id": "01929b7e-0000-7000-8000-000000000401",
    "replaced_by": null,
    "items": [
      {
        "id": "01929b7e-0000-7000-8000-000000000501",
        "position": 1,
        "medication": "Amoxicilina 500 mg",
        "dose": "1 comprimido",
        "instructions": "Cada 8 horas durante 7 días."
      }
    ]
  }
}

Answers: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/record-events

Who looked at this clinical record

The PHI access log for one patient, newest first: who, what object, which action (view | create | amend | sign | export | print | delete | share), when, and from where (route + user agent). Refused attempts are here too, with detail.refused. Requires BOTH clinic_record:read and clinic_admin:write — auditing colleagues is a compliance authority, not a clinical one. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
fromquerystringnodate-time
toquerystringnodate-time
actionqueryview \create \amend \
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/record-events \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "data": [
      {
        "id": "496",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "actor_kind": "api_key",
        "actor_id": "c1c1c1c1-0000-4000-8000-000000000001",
        "actor_label": null,
        "action": "view",
        "entity": "clinic_record_access_log",
        "entity_id": null,
        "route": "GET /clinic/patients/:id/record-events",
        "user_agent": "node",
        "request_id": "088e29b0-0000-4000-8000-000000000001",
        "at": "2026-09-22T22:21:17.633Z",
        "detail": {}
      },
      {
        "id": "495",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "actor_kind": "user",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "[email protected]",
        "action": "amend",
        "entity": "clinic_specialty_chart_finding",
        "entity_id": "e29e8d94-0000-4000-8000-000000000001",
        "route": "POST /clinic/chart-findings/:findingId/reopen",
        "user_agent": "node",
        "request_id": "088e29b0-0000-4000-8000-000000000002",
        "at": "2026-09-22T22:21:17.620Z",
        "detail": {}
      },
      {
        "id": "494",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
        "actor_kind": "user",
        "actor_id": "11111111-0000-4000-8000-000000000001",
        "actor_label": "[email protected]",
        "action": "amend",
        "entity": "clinic_specialty_chart_finding",
        "entity_id": "e29e8d94-0000-4000-8000-000000000001",
        "route": "POST /clinic/chart-findings/:findingId/resolve",
        "user_agent": "node",
        "request_id": "088e29b0-0000-4000-8000-000000000003",
        "at": "2026-09-22T22:21:17.612Z",
        "detail": {}
      }
    ],
    "total": 28,
    "page": 1,
    "limit": 50
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/patients/{id}/record-events/export

The access log as CSV

The artefact handed to a patient exercising their Ley 21.719 access right, or to a reviewer. Logged as an export, which is a different disclosure from a view: a file that leaves the building is not a screen somebody looked at.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/record-events/export \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/prescriptions/{id}

One receta

The receta with its items and status. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "01929b7e-0000-7000-8000-000000000101",
    "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
    "encounter_id": null,
    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
    "kind": "simple",
    "notes": "Tomar con alimentos.",
    "replaces_prescription_id": null,
    "replace_reason": null,
    "status": "signed",
    "signed_at": "2026-11-02T13:42:00.000Z",
    "prescriber": {
      "id": "01929b7e-0000-7000-8000-000000000301",
      "nombre": "Profesional Ejemplo",
      "especialidad": "Endodoncia"
    },
    "document_id": "01929b7e-0000-7000-8000-000000000401",
    "replaced_by": null,
    "items": [
      {
        "id": "01929b7e-0000-7000-8000-000000000501",
        "position": 1,
        "medication": "Amoxicilina 500 mg",
        "dose": "1 comprimido",
        "instructions": "Cada 8 horas durante 7 días."
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

PATCH /clinic/prescriptions/{id}

Edit a DRAFT receta

Drafts only. items, when present, replaces every item. A signed receta answers 400 with reason: signed_immutable — correct it with a new receta that replaces it. The database refuses the edit too. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
prescriber_professional_iduuid—
encounter_idstring | null—
notesstring | null—máx. 2000
itemsobject[]—
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notes": "Tomar con alimentos. Control en 7 días.",
    "items": [
      {
        "medication": "Amoxicilina 500 mg",
        "dose": "1 comprimido",
        "instructions": "Cada 8 horas durante 7 días."
      }
    ]
  }'

Example response (200)

{
  "data": {
    "id": "01929b7e-0000-7000-8000-000000000101",
    "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
    "encounter_id": null,
    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
    "kind": "simple",
    "notes": "Tomar con alimentos.",
    "replaces_prescription_id": null,
    "replace_reason": null,
    "status": "signed",
    "signed_at": "2026-11-02T13:42:00.000Z",
    "prescriber": {
      "id": "01929b7e-0000-7000-8000-000000000301",
      "nombre": "Profesional Ejemplo",
      "especialidad": "Endodoncia"
    },
    "document_id": "01929b7e-0000-7000-8000-000000000401",
    "replaced_by": null,
    "items": [
      {
        "id": "01929b7e-0000-7000-8000-000000000501",
        "position": 1,
        "medication": "Amoxicilina 500 mg",
        "dose": "1 comprimido",
        "instructions": "Cada 8 horas durante 7 días."
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/prescriptions/{id}/discard

Discard a DRAFT receta

Deletes an unsigned draft. A signed receta is never deleted (400, reason: signed_immutable). Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/discard \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "01929b7e-0000-7000-8000-000000000101",
    "discarded": true
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/prescriptions/{id}/pdf

The receta as a PDF

A signed receta answers with the very bytes filed at signing (logged as print). A draft answers with a preview drawn now, marked «BORRADOR · SIN VALIDEZ COMO RECETA», stored nowhere. Cache-Control: no-store. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/pdf \
  -H "Authorization: Bearer $VITRINA_KEY"

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/prescriptions/{id}/send

Send a signed receta to the patient (WhatsApp or email)

Only a SIGNED receta that no newer receta has replaced (409 not_signed | replaced). What leaves Vitrina is a LINK, never the PDF: the message names the patient’s first name and the clinic, and never a medication. The link is a signed, expiring capability (14 days) whose receta row is re-read on every open. channel: "whatsapp" goes through the patient’s own conversation and only while Meta’s 24 h window is open (409 whatsapp_window_closed | no_whatsapp_thread | no_phone) — the same rule as the presupuesto send; channel: "email" needs an address on the ficha (409 no_email). A failed delivery is 409 delivery_failed and records no send. The send is recorded in the access log as action share (who, when, channel, a masked recipient hint) and in the audit log. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes

Body

FieldTypeRequiredConstraints
channelwhatsapp \emailyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/send \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp"
  }'

Example response (200)

{
  "data": {
    "prescription_id": "01929b7e-0000-7000-8000-000000000101",
    "channel": "whatsapp",
    "recipient_hint": "+56•••••4567",
    "sent_at": "2026-11-02T14:05:00.000Z",
    "link_expires_at": "2026-11-16T14:05:00.000Z"
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/prescriptions/{id}/sends

When and how a receta was sent to the patient

Delivered sends of this receta, newest first: channel, a masked recipient hint, when, and who sent it. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/sends \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": [
    {
      "channel": "whatsapp",
      "recipient_hint": "+56•••••4567",
      "sent_at": "2026-11-02T14:05:00.000Z",
      "sent_by": "[email protected]"
    }
  ]
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/prescriptions/{id}/sign

Sign (issue) a receta

Freezes the receta, its items and the prescriber’s details; renders the PDF with the clinic’s branding and files it on the ficha as a clinic document of kind receta — in one transaction with the signature. Signed by the prescriber in person: the member’s e-mail must be the prescribing professional’s e-mail on the roster (403 reason: prescriber_not_signer); an API key is refused. Signing twice answers 400 reason: already_signed; a draft edited while the paper was being drawn answers 400 reason: draft_changed and nothing is filed. The filed PDF cannot be deleted by hand (receta_paper_locked). Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
idpathuuidyes
curl -X POST https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/sign \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "id": "01929b7e-0000-7000-8000-000000000101",
    "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
    "encounter_id": null,
    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
    "kind": "simple",
    "notes": "Tomar con alimentos.",
    "replaces_prescription_id": null,
    "replace_reason": null,
    "status": "signed",
    "signed_at": "2026-11-02T13:42:00.000Z",
    "prescriber": {
      "id": "01929b7e-0000-7000-8000-000000000301",
      "nombre": "Profesional Ejemplo",
      "especialidad": "Endodoncia"
    },
    "document_id": "01929b7e-0000-7000-8000-000000000401",
    "replaced_by": null,
    "items": [
      {
        "id": "01929b7e-0000-7000-8000-000000000501",
        "position": 1,
        "medication": "Amoxicilina 500 mg",
        "dose": "1 comprimido",
        "instructions": "Cada 8 horas durante 7 días."
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/record/agenda-flags

Agenda-shown alerts for a page of patients

The agenda block tooltip. Gated on clinic:read, NOT clinic_record:read: a flag marked shown_on_agenda is one the clinic decided everybody who can see the diary should read, and a receptionist who may not open the ficha still has to know not to seat this patient without an abono. Nothing else about the record comes back. At most 200 patient ids per call, and each disclosed patient gets an access-log row.

Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.

ParameterInTypeRequiredConstraints
patient_idsquerystringyesmín. 1, máx. 8000
curl https://api.vitrinadev.com/api/v1/clinic/record/agenda-flags \
  -H "Authorization: Bearer $VITRINA_KEY"

Example response (200)

{
  "data": {
    "b0e5fda8-6324-4bbd-894c-8722acddf7b8": [
      {
        "id": "bb4c2278-0000-4000-8000-000000000001",
        "kind": "alergia",
        "label": "Alergia a penicilina",
        "severity": "severa"
      }
    ]
  }
}

Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429

On this page

POST /clinic/antecedentes/{id}/supersedePOST /clinic/appointments/{id}/attendGET /clinic/chart-findings/{findingId}/plan-itemPOST /clinic/chart-findings/{findingId}/plan-itemPOST /clinic/chart-findings/{findingId}/reopenPOST /clinic/chart-findings/{findingId}/resolveGET /clinic/charts/{chartId}/findingsPOST /clinic/charts/{chartId}/findingsGET /clinic/charts/{chartId}/versionsGET /clinic/charts/{chartId}/versions/{version}GET /clinic/encounters/{id}POST /clinic/encounters/{id}/closeGET /clinic/encounters/{id}/notesPOST /clinic/encounters/{id}/notesPUT /clinic/encounters/{id}/sterilization-lotePOST /clinic/encounters/{id}/voidPOST /clinic/flags/{id}/resolveGET /clinic/forms/{id}PATCH /clinic/forms/{id}POST /clinic/forms/{id}/amendPOST /clinic/forms/{id}/linkPOST /clinic/forms/{id}/submitGET /clinic/forms/pendingGET /clinic/forms/requirementsPATCH /clinic/notes/{id}POST /clinic/notes/{id}/amendPOST /clinic/notes/{id}/signGET /clinic/patients/{id}/antecedentesPOST /clinic/patients/{id}/antecedentesGET /clinic/patients/{id}/chart-findingsGET /clinic/patients/{id}/chartsGET /clinic/patients/{id}/charts/{kind}POST /clinic/patients/{id}/charts/{kind}/versionsGET /clinic/patients/{id}/encountersPOST /clinic/patients/{id}/encountersPOST /clinic/patients/{id}/flagsGET /clinic/patients/{id}/formsPOST /clinic/patients/{id}/formsGET /clinic/patients/{id}/historyGET /clinic/patients/{id}/prescriptionsPOST /clinic/patients/{id}/prescriptionsGET /clinic/patients/{id}/record-eventsGET /clinic/patients/{id}/record-events/exportGET /clinic/prescriptions/{id}PATCH /clinic/prescriptions/{id}POST /clinic/prescriptions/{id}/discardGET /clinic/prescriptions/{id}/pdfPOST /clinic/prescriptions/{id}/sendGET /clinic/prescriptions/{id}/sendsPOST /clinic/prescriptions/{id}/signGET /clinic/record/agenda-flags