VitrinaAPI

Ficha clínica

Lo que se le hizo a una persona y por qué: atenciones, evoluciones firmadas, antecedentes, alertas, fichas y el odontograma con su historia. Marcada como sensible.

Beta
Puede cambiar en cualquier momento, con una entrada en el changelog y aviso a quienes la llamaron recientemente — ver versionado.

Descarga la proyección completa de la API pública: openapi.json.

Registrar la atención de un paciente explica este recurso en prosa, con ejemplos ejecutables.

MétodoRutaQué hace
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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
reasonstring | nullmá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"
    }
  }'

Ejemplo de respuesta (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"
    }
  }
}

Responde: 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.

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

Ejemplo de respuesta (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
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
findingIdpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
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
  }'

Ejemplo de respuesta (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"
  }
}

Responde: 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.

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

Ejemplo de respuesta (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"
  }
}

Responde: 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.

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

Ejemplo de respuesta (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"
  }
}

Responde: 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.

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

Ejemplo de respuesta (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
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
chartIdpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
tooth_or_zonestringmín. 1, máx. 40
codestringmín. 1, máx. 80
surfacesstring[]
labelstring | nullmáx. 200
notestring | nullmá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"
  }'

Ejemplo de respuesta (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"
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
chartIdpathuuid
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"

Ejemplo de respuesta (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"
    }
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
chartIdpathuuid
versionpathinteger≥ 1
curl https://api.vitrinadev.com/api/v1/clinic/charts/<id>/versions/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (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"
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/clinic/encounters/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (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": []
  }
}

Responde: 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.

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

Ejemplo de respuesta (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"
        }
      }
    ]
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/clinic/encounters/<id>/notes \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (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
      }
    ]
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
bodystringmí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"
  }'

Ejemplo de respuesta (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
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
reasonstring | nullmá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"
  }'

Ejemplo de respuesta (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"
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/clinic/forms/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (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?"
            }
          ]
        }
      ]
    }
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
valuesobject
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"
    }
  }'

Ejemplo de respuesta (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?"
            }
          ]
        }
      ]
    }
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
valuesobject
reasonstringmí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"
  }'

Ejemplo de respuesta (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?"
            }
          ]
        }
      ]
    }
  }
}

Responde: 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.

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

Ejemplo de respuesta (200)

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

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
valuesobject
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"
    }
  }'

Ejemplo de respuesta (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?"
            }
          ]
        }
      ]
    }
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
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"

Ejemplo de respuesta (200)

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

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
clinic_patient_idqueryuuid
service_idsquerystringmín. 1
curl https://api.vitrinadev.com/api/v1/clinic/forms/requirements \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

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

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
bodystringmí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."
  }'

Ejemplo de respuesta (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
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
bodystringmín. 1, máx. 20000
reasonstringmí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"
  }'

Ejemplo de respuesta (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"
    }
  }
}

Responde: 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.

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

Ejemplo de respuesta (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
  }
}

Responde: 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.

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

Ejemplo de respuesta (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": []
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
kindalergia \patologia \medicamento \
labelstringmín. 1, máx. 200
detailstring | nullmáx. 2000
severityleve \moderada \severa \
statusactive \resolved
onset_onstring | nullpatró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"
  }'

Ejemplo de respuesta (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"
  }
}

Responde: 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.

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

Ejemplo de respuesta (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
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/charts \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (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
  }
}

Responde: 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.

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

Ejemplo de respuesta (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": []
    }
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
kindpathodontograma \periodontograma \mapa_facial \

Cuerpo

CampoTipoObligatorioRestricciones
stateobject
notationfdi \palmer \universal \
encounter_idstring | null
summarystring | nullmá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"
    }
  }'

Ejemplo de respuesta (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": []
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
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"

Ejemplo de respuesta (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
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
appointment_idstring | null
professional_idstring | null
location_idstring | null
form_template_idstring | null
treatment_plan_idstring | null
kindconsulta \sesion \control \
starts_atstring | nulldate-time
ends_atstring | nulldate-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"
  }'

Ejemplo de respuesta (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": []
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
kindalergia \riesgo \vip \
labelstringmín. 1, máx. 200
detailstring | nullmá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
  }'

Ejemplo de respuesta (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"
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
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"

Ejemplo de respuesta (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
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
template_iduuid
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
  }'

Ejemplo de respuesta (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?"
            }
          ]
        }
      ]
    }
  }
}

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/history \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

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

Responde: 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.

ParámetroEnTipoObligatorioRestricciones
idpathuuid
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"

Ejemplo de respuesta (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
  }
}

Responde: 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.

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

Responde: 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.

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

Ejemplo de respuesta (200)

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

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

En esta página