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 |
POST | /clinic/appointments/{id}/attend | «Atender»: open the cita's atención and move it into the box |
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 |
PUT | /clinic/encounters/{id}/sterilization-lote | Record the sterilised-pack lote used on an atención |
POST | /clinic/encounters/{id}/void | Void an atención (idempotent) |
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}/prescriptions | The recetas on one patient’s ficha |
POST | /clinic/patients/{id}/prescriptions | Write a receta (and optionally sign it) |
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/prescriptions/{id} | One receta |
PATCH | /clinic/prescriptions/{id} | Edit a DRAFT receta |
POST | /clinic/prescriptions/{id}/discard | Discard a DRAFT receta |
GET | /clinic/prescriptions/{id}/pdf | The receta as a PDF |
POST | /clinic/prescriptions/{id}/send | Send a signed receta to the patient (WhatsApp or email) |
GET | /clinic/prescriptions/{id}/sends | When and how a receta was sent to the patient |
POST | /clinic/prescriptions/{id}/sign | Sign (issue) a receta |
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
POST /clinic/appointments/{id}/attend
«Atender»: open the cita's atención and move it into the box
One call from a cita to its clinical record. Resolves the cita's patient (its own ficha, else the contact's), opens the atención LINKED to the cita — IDEMPOTENT: a second call returns the same atención — with the cita's professional, sede and hour, and moves the sala de espera to in_room when the patient is still scheduled or waiting and the caller holds clinic:write. Answers \{ encounter, clinic_patient_id, contact_id, arrival_state \}. Requires clinic_record:write AND the per-patient PHI gate; refuses a bloqueo, a cancelled or no-show cita, and a cita with no ficha. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/appointments/<id>/attend \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"encounter": {
"id": "e0e57121-0000-4000-8000-0000000007ac",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"appointment_id": "d93e8eec-0000-4000-8000-000000000001",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"treatment_plan_id": null,
"form_template_id": null,
"sterilization_lote": null,
"kind": "control",
"status": "open",
"starts_at": "2026-10-09T18:00:00.000Z",
"ends_at": null,
"closed_at": null,
"opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
"closed_by_user_id": null,
"voided_reason": null,
"author": null,
"created_at": "2026-10-09T18:00:00.000Z",
"updated_at": "2026-10-09T18:00:00.000Z",
"notes": []
},
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "c0c0c0c0-0000-4000-8000-000000000001",
"arrival_state": "in_room"
}
}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. Imported versions (the history carried across from Dentalink) are the oldest — the end of this newest-first list — and carry imported_from / imported_as_of.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| 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. performed_plan_item_ids (with sign: true and the clinic_money:write scope) marks those pending treatment-plan lines of this patient «realizada» in the same transaction and freezes what was performed in structured.performed_plan_items; a line of another patient is a 400 and a line that is no longer pending is a 409. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| 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 | — | |
performed_plan_item_ids | uuid[] | — |
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
PUT /clinic/encounters/{id}/sterilization-lote
Record the sterilised-pack lote used on an atención
Sets, changes or clears (lote: null) the optional lote of the sterilised instrument pack used on this atención (#4004). Free text: it is matched to an Esterilización cycle by value, not by id, so a lote logged on paper or in Dentalink can still be recorded. Refused on a voided or locked atención. The lote is not copied into the audit row. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
lote | string | null | sí | máx. 60 |
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/encounters/<id>/sterilization-lote \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"lote": "A-0412"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "e0e57121-0000-4000-8000-0000000006ab",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"appointment_id": null,
"professional_id": null,
"location_id": null,
"treatment_plan_id": null,
"form_template_id": null,
"sterilization_lote": "A-0412",
"kind": "procedimiento",
"status": "open",
"starts_at": "2026-10-09T18:00:00.000Z",
"ends_at": null,
"closed_at": null,
"opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
"closed_by_user_id": null,
"voided_reason": null,
"author": null,
"created_at": "2026-10-09T18:00:00.000Z",
"updated_at": "2026-10-09T18:05:00.000Z",
"notes": []
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/encounters/{id}/void
Void an atención (idempotent)
Voids an atención opened or closed in error; the row stays, with its reason. Voiding an already voided atención returns it unchanged. Refused on a locked atención. The cita lines that closing performed go back to not performed, so their insumos return to stock. The reason is not copied into the audit row. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
reason | string | sí | mín. 1, máx. 500 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/encounters/<id>/void \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Abierta por error"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "e0e57121-0000-4000-8000-0000000007ab",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"appointment_id": "d93e8eec-0000-4000-8000-000000000001",
"professional_id": null,
"location_id": null,
"treatment_plan_id": null,
"form_template_id": null,
"sterilization_lote": null,
"kind": "control",
"status": "voided",
"starts_at": "2026-10-09T18:00:00.000Z",
"ends_at": null,
"closed_at": "2026-10-09T18:40:00.000Z",
"opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
"closed_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
"voided_reason": "Abierta por error",
"author": null,
"created_at": "2026-10-09T18:00:00.000Z",
"updated_at": "2026-10-09T18:45:00.000Z",
"notes": []
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/flags/{id}/resolve
Lift a patient alert
The row stays — who raised it and why is part of the record. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| 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. A version carried across from the clinic's previous system by an instructed migration run has imported_from set (healthatom) and imported_as_of = the date that system showed («Importado de Dentalink · <fecha>»); it is read-only like every version, and a save draws the next version on top of it.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| 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 | — | |
sterilization_lote | string | null | — | máx. 60 |
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}/prescriptions
The recetas on one patient’s ficha
data holds the native recetas, newest first, each with its items, its status (draft | signed), last_send (the newest delivered send of a signed receta — channel, a masked recipient_hint, sent_at, sent_by — or null) and — when a newer receta corrected it — replaced_by. documents holds the patient’s OTHER recetas: clinic documents of kind receta that no native receta owns (imported from the previous system, or uploaded by hand), so both kinds read together in one disclosure. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/prescriptions \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"data": [
{
"id": "01929b7e-0000-7000-8000-000000000101",
"clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
"encounter_id": null,
"prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
"kind": "simple",
"notes": "Tomar con alimentos.",
"replaces_prescription_id": null,
"replace_reason": null,
"status": "signed",
"signed_at": "2026-11-02T13:42:00.000Z",
"prescriber": {
"id": "01929b7e-0000-7000-8000-000000000301",
"nombre": "Profesional Ejemplo",
"especialidad": "Endodoncia"
},
"document_id": "01929b7e-0000-7000-8000-000000000401",
"replaced_by": null,
"items": [
{
"id": "01929b7e-0000-7000-8000-000000000501",
"position": 1,
"medication": "Amoxicilina 500 mg",
"dose": "1 comprimido",
"instructions": "Cada 8 horas durante 7 días."
}
]
}
],
"documents": []
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/patients/{id}/prescriptions
Write a receta (and optionally sign it)
Free-text items (medication, dose, instructions; 1–20), the prescribing professional (required), an optional atención and general notes. Born a draft unless sign: true, which signs it in the same call — the ordinary path for a dentist. Only a member signs: an API key may prepare a draft and is refused sign: true. To CORRECT a signed receta, write a new one with replaces_prescription_id and a mandatory replace_reason; a receta is replaced at most once. Controlled and retained recetas are issued outside Vitrina. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
prescriber_professional_id | uuid | sí | |
encounter_id | string | null | — | |
notes | string | null | — | máx. 2000 |
items | object[] | sí | |
replaces_prescription_id | string | null | — | |
replace_reason | string | null | — | máx. 500 |
sign | boolean | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/prescriptions \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
"notes": "Tomar con alimentos.",
"items": [
{
"medication": "Amoxicilina 500 mg",
"dose": "1 comprimido",
"instructions": "Cada 8 horas durante 7 días."
}
],
"sign": true
}'Ejemplo de respuesta (201)
{
"data": {
"id": "01929b7e-0000-7000-8000-000000000101",
"clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
"encounter_id": null,
"prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
"kind": "simple",
"notes": "Tomar con alimentos.",
"replaces_prescription_id": null,
"replace_reason": null,
"status": "signed",
"signed_at": "2026-11-02T13:42:00.000Z",
"prescriber": {
"id": "01929b7e-0000-7000-8000-000000000301",
"nombre": "Profesional Ejemplo",
"especialidad": "Endodoncia"
},
"document_id": "01929b7e-0000-7000-8000-000000000401",
"replaced_by": null,
"items": [
{
"id": "01929b7e-0000-7000-8000-000000000501",
"position": 1,
"medication": "Amoxicilina 500 mg",
"dose": "1 comprimido",
"instructions": "Cada 8 horas durante 7 días."
}
]
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/patients/{id}/record-events
Who looked at this clinical record
The PHI access log for one patient, newest first: who, what object, which action (view | create | amend | sign | export | print | delete | share), when, and from where (route + user agent). Refused attempts are here too, with detail.refused. Requires BOTH clinic_record:read and clinic_admin:write — auditing colleagues is a compliance authority, not a clinical one. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| 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/prescriptions/{id}
One receta
The receta with its items and status. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "01929b7e-0000-7000-8000-000000000101",
"clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
"encounter_id": null,
"prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
"kind": "simple",
"notes": "Tomar con alimentos.",
"replaces_prescription_id": null,
"replace_reason": null,
"status": "signed",
"signed_at": "2026-11-02T13:42:00.000Z",
"prescriber": {
"id": "01929b7e-0000-7000-8000-000000000301",
"nombre": "Profesional Ejemplo",
"especialidad": "Endodoncia"
},
"document_id": "01929b7e-0000-7000-8000-000000000401",
"replaced_by": null,
"items": [
{
"id": "01929b7e-0000-7000-8000-000000000501",
"position": 1,
"medication": "Amoxicilina 500 mg",
"dose": "1 comprimido",
"instructions": "Cada 8 horas durante 7 días."
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/prescriptions/{id}
Edit a DRAFT receta
Drafts only. items, when present, replaces every item. A signed receta answers 400 with reason: signed_immutable — correct it with a new receta that replaces it. The database refuses the edit too. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
prescriber_professional_id | uuid | — | |
encounter_id | string | null | — | |
notes | string | null | — | máx. 2000 |
items | object[] | — |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"notes": "Tomar con alimentos. Control en 7 días.",
"items": [
{
"medication": "Amoxicilina 500 mg",
"dose": "1 comprimido",
"instructions": "Cada 8 horas durante 7 días."
}
]
}'Ejemplo de respuesta (200)
{
"data": {
"id": "01929b7e-0000-7000-8000-000000000101",
"clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
"encounter_id": null,
"prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
"kind": "simple",
"notes": "Tomar con alimentos.",
"replaces_prescription_id": null,
"replace_reason": null,
"status": "signed",
"signed_at": "2026-11-02T13:42:00.000Z",
"prescriber": {
"id": "01929b7e-0000-7000-8000-000000000301",
"nombre": "Profesional Ejemplo",
"especialidad": "Endodoncia"
},
"document_id": "01929b7e-0000-7000-8000-000000000401",
"replaced_by": null,
"items": [
{
"id": "01929b7e-0000-7000-8000-000000000501",
"position": 1,
"medication": "Amoxicilina 500 mg",
"dose": "1 comprimido",
"instructions": "Cada 8 horas durante 7 días."
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/prescriptions/{id}/discard
Discard a DRAFT receta
Deletes an unsigned draft. A signed receta is never deleted (400, reason: signed_immutable). Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/discard \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "01929b7e-0000-7000-8000-000000000101",
"discarded": true
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/prescriptions/{id}/pdf
The receta as a PDF
A signed receta answers with the very bytes filed at signing (logged as print). A draft answers with a preview drawn now, marked «BORRADOR · SIN VALIDEZ COMO RECETA», stored nowhere. Cache-Control: no-store. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/pdf \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/prescriptions/{id}/send
Send a signed receta to the patient (WhatsApp or email)
Only a SIGNED receta that no newer receta has replaced (409 not_signed | replaced). What leaves Vitrina is a LINK, never the PDF: the message names the patient’s first name and the clinic, and never a medication. The link is a signed, expiring capability (14 days) whose receta row is re-read on every open. channel: "whatsapp" goes through the patient’s own conversation and only while Meta’s 24 h window is open (409 whatsapp_window_closed | no_whatsapp_thread | no_phone) — the same rule as the presupuesto send; channel: "email" needs an address on the ficha (409 no_email). A failed delivery is 409 delivery_failed and records no send. The send is recorded in the access log as action share (who, when, channel, a masked recipient hint) and in the audit log. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
channel | whatsapp \ | email | sí |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/send \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp"
}'Ejemplo de respuesta (200)
{
"data": {
"prescription_id": "01929b7e-0000-7000-8000-000000000101",
"channel": "whatsapp",
"recipient_hint": "+56•••••4567",
"sent_at": "2026-11-02T14:05:00.000Z",
"link_expires_at": "2026-11-16T14:05:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/prescriptions/{id}/sends
When and how a receta was sent to the patient
Delivered sends of this receta, newest first: channel, a masked recipient hint, when, and who sent it. Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/sends \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": [
{
"channel": "whatsapp",
"recipient_hint": "+56•••••4567",
"sent_at": "2026-11-02T14:05:00.000Z",
"sent_by": "[email protected]"
}
]
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/prescriptions/{id}/sign
Sign (issue) a receta
Freezes the receta, its items and the prescriber’s details; renders the PDF with the clinic’s branding and files it on the ficha as a clinic document of kind receta — in one transaction with the signature. Signed by the prescriber in person: the member’s e-mail must be the prescribing professional’s e-mail on the roster (403 reason: prescriber_not_signer); an API key is refused. Signing twice answers 400 reason: already_signed; a draft edited while the paper was being drawn answers 400 reason: draft_changed and nothing is filed. The filed PDF cannot be deleted by hand (receta_paper_locked). Requires clinic_record:read (reads) or clinic_record:write (writes) AND the per-patient PHI gate. Every call writes a clinic_record_access_log row; a call that cannot write its access event fails.
Connected apps: refused with 403 CONNECTED_APP_SENSITIVE_DATA, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.
| Parámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/prescriptions/<id>/sign \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "01929b7e-0000-7000-8000-000000000101",
"clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
"encounter_id": null,
"prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
"kind": "simple",
"notes": "Tomar con alimentos.",
"replaces_prescription_id": null,
"replace_reason": null,
"status": "signed",
"signed_at": "2026-11-02T13:42:00.000Z",
"prescriber": {
"id": "01929b7e-0000-7000-8000-000000000301",
"nombre": "Profesional Ejemplo",
"especialidad": "Endodoncia"
},
"document_id": "01929b7e-0000-7000-8000-000000000401",
"replaced_by": null,
"items": [
{
"id": "01929b7e-0000-7000-8000-000000000501",
"position": 1,
"medication": "Amoxicilina 500 mg",
"dose": "1 comprimido",
"instructions": "Cada 8 horas durante 7 días."
}
]
}
}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
Profesionales de la clínica
Quién atiende en la clínica, con qué especialidad y con qué bloque por defecto — el id que la agenda usa como lane.
Reembolsos de seguros
La solicitud de reembolso al seguro por atención, con sus documentos y estados (por enviar, enviada, pagada, rechazada), los pagos del seguro y su conciliación con las solicitudes que saldan, parciales incluidos. El dinero del seguro nunca se cuenta dos veces: no es un pago de Caja ni del espejo de ingresos.