Registro de pacientes
Quién es paciente de la clínica y quién responde por él: la ficha de identidad, los duplicados y los vínculos de familia. Marcada como sensible — pide su propio permiso y ninguna aplicación conectada la alcanza.
Descarga la proyección completa de la API pública: openapi.json.
Identificar y fusionar pacientes explica este recurso en prosa, con ejemplos ejecutables.
| Método | Ruta | Qué hace |
|---|---|---|
GET | /clinic/contacts/{contactId}/patient | The contact page's Paciente card |
GET | /clinic/contacts/{contactId}/relationships | Every patient one contact acts for |
GET | /clinic/patients | List clinic patients |
POST | /clinic/patients | Create a native patient |
DELETE | /clinic/patients/{id} | Delete a native patient |
GET | /clinic/patients/{id} | Get a clinic patient |
PATCH | /clinic/patients/{id} | Update a patient |
GET | /clinic/patients/{id}/attendance | A patient's attendance history |
GET | /clinic/patients/{id}/citas | A patient's mirrored citas, newest first |
POST | /clinic/patients/{id}/merge | Merge a patient into another (tombstone, never a delete) |
GET | /clinic/patients/{id}/relationships | The family of one patient (apoderados, cargas, pareja) |
POST | /clinic/patients/{id}/relationships | Add somebody to a patient’s family |
GET | /clinic/patients/duplicates | Possible duplicate patients, grouped with the reason |
DELETE | /clinic/relationships/{id} | Remove a family bond |
PATCH | /clinic/relationships/{id} | Edit one family bond |
GET /clinic/contacts/{contactId}/patient
The contact page's Paciente card
The linked patient plus its next and last cita. 404 not_linked when the contact is not a patient.
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 |
|---|---|---|---|---|
contactId | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/contacts/<id>/patient \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"patient": {
"id": "b1b0b8de-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"rut": "11.111.111-1",
"nombre": "María José",
"apellidos": "Fuentes Lagos",
"email": "[email protected]",
"phone": "+56987654321",
"birthdate": "1990-04-12",
"nombre_social": null,
"prevision": "Isapre Consalud",
"sexo": "F",
"numero_ficha": "44110",
"ciudad": null,
"region": null,
"comuna": "Maipú",
"direccion": null,
"description": null,
"enabled": true,
"synced_at": null,
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"created_at": "2026-09-22T22:21:14.311Z",
"updated_at": "2026-09-22T22:21:17.194Z"
},
"next_cita": {
"id": "d93e8eec-0000-4000-8000-000000000001",
"display_id": "A-1",
"engine": "native",
"external_id": null,
"starts_at": "2026-10-01T13:00:00.000Z",
"ends_at": "2026-10-01T13:30:00.000Z",
"status": "confirmed",
"estado_nombre": null,
"profesional": "Ana Rojas Vidal",
"tratamiento": null,
"sucursal": "Sucursal Maipú",
"sillon": null,
"motivo": null,
"comentarios": null
},
"last_cita": null,
"attendance": {
"completed": 0,
"no_show": 0,
"cancelled": 0
},
"recent_citas": [
{
"id": "d93e8eec-0000-4000-8000-000000000001",
"display_id": "A-1",
"engine": "native",
"external_id": null,
"starts_at": "2026-10-01T13:00:00.000Z",
"ends_at": "2026-10-01T13:30:00.000Z",
"status": "confirmed",
"estado_nombre": null,
"profesional": "Ana Rojas Vidal",
"tratamiento": null,
"sucursal": "Sucursal Maipú",
"sillon": null,
"motivo": null,
"comentarios": null
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/contacts/{contactId}/relationships
Every patient one contact acts for
The conversation side of the same table, and what the contact ficha’s «Familia y apoderados» block renders. A contact with no clinical bonds answers an empty list, not a 404.
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 |
|---|---|---|---|---|
contactId | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/contacts/<id>/relationships \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"data": [
{
"id": "6a73a912-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"related_patient_id": null,
"role": "titular",
"is_primary": true,
"can_book": true,
"can_read_record": false,
"verified_method": "phone_match",
"verified_at": "2026-09-22T22:21:14.390Z",
"expires_at": null,
"note": null,
"created_by": null,
"created_at": "2026-09-22T22:21:14.389Z",
"updated_at": "2026-09-22T22:21:14.389Z",
"contact_name": "María José Fuentes Lagos",
"contact_phone": "+56987654321",
"patient_name": "María José Fuentes Lagos",
"expired": false
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/patients
List clinic patients
search (min 2 chars) matches the name accent-insensitively, a normalized-RUT prefix, or a phone-digits substring. Rows carry contact_id — the linked CRM contact, or null.
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 |
|---|---|---|---|---|
search | query | string | no | mín. 2, máx. 120 |
sort | query | nombre \ | rut \ | phone \ |
dir | query | asc \ | desc | 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 \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"data": [
{
"id": "b1b0b8de-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"rut": "11.111.111-1",
"nombre": "María José",
"apellidos": "Fuentes Lagos",
"email": "[email protected]",
"phone": "+56987654321",
"birthdate": "1990-04-12",
"nombre_social": null,
"prevision": "Fonasa",
"sexo": "F",
"numero_ficha": null,
"ciudad": null,
"region": null,
"comuna": "Maipú",
"direccion": null,
"description": null,
"enabled": true,
"synced_at": null,
"contact_id": null,
"created_at": "2026-09-22T22:21:14.311Z",
"updated_at": "2026-09-22T22:21:14.311Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/patients
Create a native patient
Create one of the clinic's OWN (native) patients — source is 'native'. 409 not_native_source when the tenant's clinic source is not 'native'. At least one of nombre/apellidos/rut is required; birthdate is a YYYY-MM-DD calendar day.
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.
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
nombre | string | null | — | máx. 160 |
apellidos | string | null | — | máx. 160 |
rut | string | null | — | máx. 20 |
email | string | null | — | máx. 160 |
phone | string | null | — | máx. 40 |
birthdate | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
nombre_social | string | null | — | máx. 160 |
prevision | string | null | — | máx. 120 |
sexo | string | null | — | máx. 40 |
numero_ficha | string | null | — | máx. 60 |
ciudad | string | null | — | máx. 120 |
region | string | null | — | máx. 120 |
comuna | string | null | — | máx. 120 |
direccion | string | null | — | máx. 240 |
description | string | null | — | máx. 2000 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"nombre": "María José",
"apellidos": "Fuentes Lagos",
"rut": "11.111.111-1",
"email": "[email protected]",
"phone": "+56987654321",
"birthdate": "1990-04-12",
"prevision": "Fonasa",
"sexo": "F",
"comuna": "Maipú"
}'Ejemplo de respuesta (201)
{
"data": {
"id": "b1b0b8de-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"rut": "11.111.111-1",
"nombre": "María José",
"apellidos": "Fuentes Lagos",
"email": "[email protected]",
"phone": "+56987654321",
"birthdate": "1990-04-12",
"nombre_social": null,
"prevision": "Fonasa",
"sexo": "F",
"numero_ficha": null,
"ciudad": null,
"region": null,
"comuna": "Maipú",
"direccion": null,
"description": null,
"enabled": true,
"synced_at": null,
"contact_id": null,
"created_at": "2026-09-22T22:21:14.311Z",
"updated_at": "2026-09-22T22:21:14.311Z"
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/patients/{id}
Delete a native patient
Native → hard-deleted (its contact link survives with a null clinic_patient_id). Synced (vendor) → 400 cannot_delete_synced.
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 DELETE https://api.vitrinadev.com/api/v1/clinic/patients/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/patients/{id}
Get a clinic patient
Get a clinic patient
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> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "b1b0b8de-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"rut": "11.111.111-1",
"nombre": "María José",
"apellidos": "Fuentes Lagos",
"email": "[email protected]",
"phone": "+56987654321",
"birthdate": "1990-04-12",
"nombre_social": null,
"prevision": "Fonasa",
"sexo": "F",
"numero_ficha": null,
"ciudad": null,
"region": null,
"comuna": "Maipú",
"direccion": null,
"description": null,
"enabled": true,
"synced_at": null,
"contact_id": null,
"created_at": "2026-09-22T22:21:14.311Z",
"updated_at": "2026-09-22T22:21:14.311Z",
"data": {},
"link": null
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/patients/{id}
Update a patient
Edit a native patient. 400 not_editable on a synced (non-native) row — those are managed in the clinic system, not Vitrina.
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 |
|---|---|---|---|
nombre | string | null | — | máx. 160 |
apellidos | string | null | — | máx. 160 |
rut | string | null | — | máx. 20 |
email | string | null | — | máx. 160 |
phone | string | null | — | máx. 40 |
birthdate | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
nombre_social | string | null | — | máx. 160 |
prevision | string | null | — | máx. 120 |
sexo | string | null | — | máx. 40 |
numero_ficha | string | null | — | máx. 60 |
ciudad | string | null | — | máx. 120 |
region | string | null | — | máx. 120 |
comuna | string | null | — | máx. 120 |
direccion | string | null | — | máx. 240 |
description | string | null | — | máx. 2000 |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/patients/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"prevision": "Isapre Consalud",
"comuna": "Maipú",
"numero_ficha": "44110"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "b1b0b8de-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"rut": "11.111.111-1",
"nombre": "María José",
"apellidos": "Fuentes Lagos",
"email": "[email protected]",
"phone": "+56987654321",
"birthdate": "1990-04-12",
"nombre_social": null,
"prevision": "Isapre Consalud",
"sexo": "F",
"numero_ficha": "44110",
"ciudad": null,
"region": null,
"comuna": "Maipú",
"direccion": null,
"description": null,
"enabled": true,
"synced_at": null,
"contact_id": null,
"created_at": "2026-09-22T22:21:14.311Z",
"updated_at": "2026-09-22T22:21:17.194Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/patients/{id}/attendance
A patient's attendance history
Attended / no-show / late-cancel / cancelled counts, the recent list, and the date of the last late notice — computed from the mirrored citas of ANY engine plus the CURRENT bucket of each cita's clinic_appointment_status. The late-cancel split uses the tenant's window (settings.clinic_attendance.late_cancel_hours, default 8); late_cancel_hours re-counts the same history against another one without changing any setting. late_cancel is a SUBSET of cancelled — never add the two. A past cita nobody closed is counted as unknown, never as attended. This endpoint has no opinion about fees.
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í | |
late_cancel_hours | query | integer | no | ≥ 1, ≤ 168 |
limit | query | integer | no | ≥ 1, ≤ 50, por defecto 10 |
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/attendance \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"counts": {
"attended": 0,
"no_show": 0,
"late_cancel": 0,
"cancelled": 0,
"unknown": 0
},
"closed_total": 0,
"no_show_rate": null,
"last_late_cancel_at": null,
"last_no_show_at": null,
"late_cancel_hours": 8,
"notice_basis": "status_change_recorded",
"recent": [],
"truncated": false,
"patient": {
"id": "b1b0b8de-0000-4000-8000-000000000001",
"source": "native",
"nombre": "María José",
"apellidos": "Fuentes Lagos"
},
"matched_by": [
"contact",
"ficha"
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/patients/{id}/citas
A patient's mirrored citas, newest first
A patient's mirrored citas, newest first
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 |
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/citas \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"data": [
{
"id": "d93e8eec-0000-4000-8000-000000000001",
"display_id": "A-1",
"engine": "native",
"external_id": null,
"starts_at": "2026-10-01T13:00:00.000Z",
"ends_at": "2026-10-01T13:30:00.000Z",
"status": "confirmed",
"estado_nombre": null,
"profesional": "Ana Rojas Vidal",
"tratamiento": null,
"sucursal": "Sucursal Maipú",
"sillon": null,
"motivo": null,
"comentarios": null
}
],
"total": 1,
"page": 1,
"limit": 50
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/patients/{id}/merge
Merge a patient into another (tombstone, never a delete)
\{id\} is the ficha that STOPS being used; into_patient_id is the one that survives. Repoints the links, the waitlist entries and the family bonds, fills the survivor's BLANK fields from the loser (never overwrites), writes a clinic_patient_merge tombstone with a snapshot of the losing ficha plus who/when/why, and marks the loser merged_into — which keeps its row and its id resolvable (Ley 21.719 + the 15-year retention floor, §H.3). REFUSES 409 vendor_identity_conflict when the loser carries a Medilink/Reservo id the survivor does not share: the next poll would re-create it, so that merge belongs in the vendor. Also 409 for an already-merged ficha on either side.
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 |
|---|---|---|---|
into_patient_id | uuid | sí | |
reason | string | — | máx. 500 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/merge \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"into_patient_id": "42db9ec9-0000-4000-8000-000000000001",
"reason": "Misma persona, cargada dos veces"
}'Ejemplo de respuesta (200)
{
"data": {
"merge_id": "14091a9f-0000-4000-8000-000000000001",
"survivor": {
"id": "42db9ec9-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"rut": "22.222.222-2",
"nombre": "Tomás",
"apellidos": "Riquelme Paz",
"email": null,
"phone": "+56911112222",
"birthdate": null,
"nombre_social": null,
"prevision": "Isapre",
"sexo": null,
"numero_ficha": null,
"ciudad": null,
"region": null,
"comuna": null,
"direccion": null,
"description": null,
"enabled": true,
"data": {},
"synced_at": null,
"merged_into": null,
"merged_at": null,
"created_at": "2026-09-22T22:21:14.418Z",
"updated_at": "2026-09-22T22:21:14.418Z"
},
"loser_id": "c6bc29c3-0000-4000-8000-000000000001",
"moved": {
"healthatom_patient_link": 0,
"waitlist_entry": 0,
"clinic_patient_relationship": 0,
"clinic_patient_relationship.related_patient_id": 0,
"clinic_pack_purchase": 0,
"clinic_encounter": 0,
"clinic_encounter_note": 0,
"clinic_history_item": 0,
"clinic_clinical_flag": 0,
"clinic_form_response": 0,
"clinic_consent": 0,
"clinic_record_photo": 0,
"clinic_document": 0,
"clinic_patient_agreement": 0,
"clinic_appointment_series": 0,
"clinic_specialty_chart": 0,
"clinic_budget": 0,
"clinic_treatment_plan": 0,
"clinic_tax_document": 0
},
"left_on_tombstone": {
"clinic_patient_relationship": 0,
"clinic_patient_agreement": 0,
"clinic_specialty_chart": 0
},
"filled_fields": []
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/patients/{id}/relationships
The family of one patient (apoderados, cargas, pareja)
Every contact bonded to this ficha, primary first. Each bond carries two SEPARATE authorities: can_book (may act on the agenda) and can_read_record (⚠ the PHI gate — may be told what is in the clinical record). expired: true marks a bond whose expires_at has passed; every gate already treats it as no bond at all.
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>/relationships \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"data": [
{
"id": "6a73a912-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"related_patient_id": null,
"role": "titular",
"is_primary": true,
"can_book": true,
"can_read_record": false,
"verified_method": "phone_match",
"verified_at": "2026-09-22T22:21:14.390Z",
"expires_at": null,
"note": null,
"created_by": null,
"created_at": "2026-09-22T22:21:14.389Z",
"updated_at": "2026-09-22T22:21:14.389Z",
"contact_name": "María José Fuentes Lagos",
"contact_phone": "+56987654321",
"patient_name": "María José Fuentes Lagos",
"expired": false
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/patients/{id}/relationships
Add somebody to a patient’s family
Creates one bond between this ficha and a contact. 409 relationship_exists when the pair is already recorded (edit it instead — quietly resetting a PHI flag is not what the caller asked for). Granting can_read_record: true additionally requires the clinic_record:write scope; everything else about a family is clinic administration and needs only clinic:write.
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 |
|---|---|---|---|
contact_id | uuid | sí | |
related_patient_id | string | null | — | |
role | titular \ | apoderado \ | tutor \ |
is_primary | boolean | — | |
can_book | boolean | — | |
can_read_record | boolean | — | |
verified_method | phone_match \ | id_challenge \ | manual \ |
expires_at | string | null | — | date-time |
note | string | null | — | máx. 500 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/patients/<id>/relationships \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"role": "titular",
"is_primary": true,
"can_book": true,
"verified_method": "phone_match"
}'Ejemplo de respuesta (201)
{
"data": {
"id": "6a73a912-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"related_patient_id": null,
"role": "titular",
"is_primary": true,
"can_book": true,
"can_read_record": false,
"verified_method": "phone_match",
"verified_at": "2026-09-22T22:21:14.390Z",
"expires_at": null,
"note": null,
"created_by": null,
"created_at": "2026-09-22T22:21:14.389Z",
"updated_at": "2026-09-22T22:21:14.389Z",
"contact_name": "María José Fuentes Lagos",
"contact_phone": "+56987654321",
"patient_name": "María José Fuentes Lagos",
"expired": false
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/patients/duplicates
Possible duplicate patients, grouped with the reason
Scans the LIVE roster (tombstones excluded) for three patterns: the same document, the same normalised phone, and the same name + date of birth. A RUT is canonicalised through the one legal-identity normaliser and a passport is never folded through it, so a mistyped RUT groups with nothing rather than colliding with a real passport. One group per candidate SET (a pair matching on two rules is one card listing both reasons), strongest reason first. vendor_blocked: true means every member carries a Medilink/Reservo id and the merge has to happen there first. truncated says the roster is larger than one scan.
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.
curl https://api.vitrinadev.com/api/v1/clinic/patients/duplicates \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"groups": [],
"scanned": 2,
"truncated": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/relationships/{id}
Remove a family bond
Hard delete, deliberately: a relationship is a live PERMISSION, not a clinical fact. The record of who ACCESSED a ficha is clinic_record_event, which is append-only and untouched by this.
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 DELETE https://api.vitrinadev.com/api/v1/clinic/relationships/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"deleted": true
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/relationships/{id}
Edit one family bond
Partial update. Promoting a bond to is_primary demotes the incumbent first (the partial unique index allows exactly one per patient). Raising can_read_record needs clinic_record:write; lowering it never does — revoking access is always safe.
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 |
|---|---|---|---|
related_patient_id | string | null | — | |
role | titular \ | apoderado \ | tutor \ |
is_primary | boolean | — | |
can_book | boolean | — | |
can_read_record | boolean | — | |
verified_method | phone_match \ | id_challenge \ | manual \ |
expires_at | string | null | — | date-time |
note | string | null | — | máx. 500 |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/relationships/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"can_read_record": false
}'Ejemplo de respuesta (200)
{
"data": {
"id": "6a73a912-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000002",
"related_patient_id": null,
"role": "apoderado",
"is_primary": false,
"can_book": true,
"can_read_record": false,
"verified_method": "id_challenge",
"verified_at": "2026-09-22T22:21:17.284Z",
"expires_at": null,
"note": "Presentó cédula en el mesón",
"created_by": null,
"created_at": "2026-09-22T22:21:17.283Z",
"updated_at": "2026-09-22T22:21:17.298Z",
"contact_name": "Carmen Lagos Pinto",
"contact_phone": "+56955554444",
"patient_name": "María José Fuentes Lagos",
"expired": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429