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.
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étodo | Ruta | Qué hace |
|---|---|---|
POST | /clinic/antecedentes/{id}/supersede | Supersede an antecedente with a successor |
GET | /clinic/chart-findings/{findingId}/plan-item | The presupuesto line this finding would become |
POST | /clinic/chart-findings/{findingId}/plan-item | Link a finding to the plan item (or cita line) it produced |
POST | /clinic/chart-findings/{findingId}/reopen | Undo «tratado» on a finding |
POST | /clinic/chart-findings/{findingId}/resolve | Mark a finding treated |
GET | /clinic/charts/{chartId}/findings | The findings painted on one chart |
POST | /clinic/charts/{chartId}/findings | Record one finding without redrawing the chart |
GET | /clinic/charts/{chartId}/versions | The 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}/close | Close an atención (idempotent) |
GET | /clinic/encounters/{id}/notes | Evoluciones of one atención, oldest first |
POST | /clinic/encounters/{id}/notes | Write an evolución |
POST | /clinic/flags/{id}/resolve | Lift 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}/amend | Amend a submitted ficha (a NEW response, with a reason) |
POST | /clinic/forms/{id}/link | Mint the tokenised link the patient fills on their phone |
POST | /clinic/forms/{id}/submit | Submit 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/requirements | Is this cita confirmable? What is still missing |
PATCH | /clinic/notes/{id} | Correct an UNSIGNED evolución in place |
POST | /clinic/notes/{id}/amend | Amend a signed evolución (a NEW row, never an edit) |
POST | /clinic/notes/{id}/sign | Sign an evolución |
GET | /clinic/patients/{id}/antecedentes | Antecedentes and patient alerts |
POST | /clinic/patients/{id}/antecedentes | Record an antecedente |
GET | /clinic/patients/{id}/chart-findings | Everything found on this patient, across every chart |
GET | /clinic/patients/{id}/charts | The 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}/versions | Save the chart — an edit creates a version |
GET | /clinic/patients/{id}/encounters | Atenciones of one patient, newest first, with their notes |
POST | /clinic/patients/{id}/encounters | Open an atención |
POST | /clinic/patients/{id}/flags | Raise a patient alert |
GET | /clinic/patients/{id}/forms | Fichas filled for one patient, newest first |
POST | /clinic/patients/{id}/forms | Open a ficha against the template’s live version |
GET | /clinic/patients/{id}/history | A patient's clinical history (Reservo) |
GET | /clinic/patients/{id}/record-events | Who looked at this clinical record |
GET | /clinic/patients/{id}/record-events/export | The access log as CSV |
GET | /clinic/record/agenda-flags | Agenda-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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
reason | string | null | — | máx. 500 |
successor | object | 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
findingId | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
findingId | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
treatment_plan_item_id | string | null | — | |
clinic_appointment_service_id | string | 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
findingId | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
findingId | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
chartId | path | uuid | sí | |
status | query | active \ | treated | no |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
chartId | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
tooth_or_zone | string | sí | mín. 1, máx. 40 |
code | string | sí | mín. 1, máx. 80 |
surfaces | string[] | — | |
label | string | null | — | máx. 200 |
note | string | null | — | máx. 2000 |
encounter_id | string | 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
chartId | path | uuid | sí | |
page | query | integer | no | ≥ 1, por defecto 1 |
limit | query | integer | no | ≥ 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
chartId | path | uuid | sí | |
version | path | integer | sí | ≥ 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
body | string | sí | mín. 1, máx. 20000 |
kind | anamnesis \ | evolucion \ | indicaciones \ |
structured | object | — | |
author_professional_id | string | null | — | |
sign | boolean | — |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
reason | string | 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"
}'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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
values | object | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
values | object | sí | |
reason | string | sí | mí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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
values | object | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
from | query | string | no | date-time |
to | query | string | no | date-time |
limit | query | integer | no | ≥ 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
clinic_patient_id | query | uuid | sí | |
service_ids | query | string | sí | mí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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
body | string | — | mín. 1, máx. 20000 |
kind | anamnesis \ | evolucion \ | indicaciones \ |
structured | object | — |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
body | string | sí | mín. 1, máx. 20000 |
reason | string | sí | mín. 3, máx. 500 |
structured | object | — |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
include_superseded | query | true \ | false | no |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
kind | alergia \ | patologia \ | medicamento \ |
label | string | sí | mín. 1, máx. 200 |
detail | string | null | — | máx. 2000 |
severity | leve \ | moderada \ | severa \ |
status | active \ | resolved | — |
onset_on | string | 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"
}'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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
status | query | active \ | treated | no |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
kind | path | odontograma \ | 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
kind | path | odontograma \ | periodontograma \ | mapa_facial \ |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
state | object | sí | |
notation | fdi \ | palmer \ | universal \ |
encounter_id | string | null | — | |
summary | string | null | — | máx. 400 |
expected_version | integer | null | — | ≥ 0 |
findings | object[] | — |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
page | query | integer | no | ≥ 1, por defecto 1 |
limit | query | integer | no | ≥ 1, ≤ 100, por defecto 25 |
include_voided | query | true \ | false | no |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
appointment_id | string | null | — | |
professional_id | string | null | — | |
location_id | string | null | — | |
form_template_id | string | null | — | |
treatment_plan_id | string | null | — | |
kind | consulta \ | sesion \ | control \ |
starts_at | string | null | — | date-time |
ends_at | string | 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"
}'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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
kind | alergia \ | riesgo \ | vip \ |
label | string | sí | mín. 1, máx. 200 |
detail | string | null | — | máx. 2000 |
severity | leve \ | moderada \ | severa \ |
shown_on_agenda | boolean | — | |
history_item_id | string | 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
encounter_id | query | uuid | no | |
page | query | integer | no | ≥ 1, por defecto 1 |
limit | query | integer | no | ≥ 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
template_id | uuid | sí | |
encounter_id | string | null | — | |
appointment_id | string | null | — | |
values | object | — | |
as_request | boolean | — |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí | |
page | query | integer | no | ≥ 1, por defecto 1 |
limit | query | integer | no | ≥ 1, ≤ 200, por defecto 50 |
from | query | string | no | date-time |
to | query | string | no | date-time |
action | query | view \ | 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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
patient_ids | query | string | sí | mí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