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
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
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}/record-eventsWho looked at this clinical record
GET/clinic/patients/{id}/record-events/exportThe access log as CSV
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

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.

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. 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—
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

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.

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—
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}/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/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