VitrinaAPI

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.

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

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

Identificar y fusionar pacientes explica este recurso en prosa, con ejemplos ejecutables.

MétodoRutaQué hace
GET/clinic/contacts/{contactId}/patientThe contact page's Paciente card
GET/clinic/contacts/{contactId}/relationshipsEvery patient one contact acts for
GET/clinic/patientsList clinic patients
POST/clinic/patientsCreate 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}/attendanceA patient's attendance history
GET/clinic/patients/{id}/citasA patient's mirrored citas, newest first
POST/clinic/patients/{id}/mergeMerge a patient into another (tombstone, never a delete)
GET/clinic/patients/{id}/relationshipsThe family of one patient (apoderados, cargas, pareja)
POST/clinic/patients/{id}/relationshipsAdd somebody to a patient’s family
GET/clinic/patients/duplicatesPossible 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ámetroEnTipoObligatorioRestricciones
contactIdpathuuid
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ámetroEnTipoObligatorioRestricciones
contactIdpathuuid
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ámetroEnTipoObligatorioRestricciones
searchquerystringnomín. 2, máx. 120
sortquerynombre \rut \phone \
dirqueryasc \descno
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 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

CampoTipoObligatorioRestricciones
nombrestring | nullmáx. 160
apellidosstring | nullmáx. 160
rutstring | nullmáx. 20
emailstring | nullmáx. 160
phonestring | nullmáx. 40
birthdatestring | nullpatrón ^\d{4}-\d{2}-\d{2}$
nombre_socialstring | nullmáx. 160
previsionstring | nullmáx. 120
sexostring | nullmáx. 40
numero_fichastring | nullmáx. 60
ciudadstring | nullmáx. 120
regionstring | nullmáx. 120
comunastring | nullmáx. 120
direccionstring | nullmáx. 240
descriptionstring | nullmá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ámetroEnTipoObligatorioRestricciones
idpathuuid
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ámetroEnTipoObligatorioRestricciones
idpathuuid
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ámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
nombrestring | nullmáx. 160
apellidosstring | nullmáx. 160
rutstring | nullmáx. 20
emailstring | nullmáx. 160
phonestring | nullmáx. 40
birthdatestring | nullpatrón ^\d{4}-\d{2}-\d{2}$
nombre_socialstring | nullmáx. 160
previsionstring | nullmáx. 120
sexostring | nullmáx. 40
numero_fichastring | nullmáx. 60
ciudadstring | nullmáx. 120
regionstring | nullmáx. 120
comunastring | nullmáx. 120
direccionstring | nullmáx. 240
descriptionstring | nullmá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ámetroEnTipoObligatorioRestricciones
idpathuuid
late_cancel_hoursqueryintegerno≥ 1, ≤ 168
limitqueryintegerno≥ 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ámetroEnTipoObligatorioRestricciones
idpathuuid
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 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ámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
into_patient_iduuid
reasonstringmá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ámetroEnTipoObligatorioRestricciones
idpathuuid
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ámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
contact_iduuid
related_patient_idstring | null
roletitular \apoderado \tutor \
is_primaryboolean
can_bookboolean
can_read_recordboolean
verified_methodphone_match \id_challenge \manual \
expires_atstring | nulldate-time
notestring | nullmá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ámetroEnTipoObligatorioRestricciones
idpathuuid
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ámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
related_patient_idstring | null
roletitular \apoderado \tutor \
is_primaryboolean
can_bookboolean
can_read_recordboolean
verified_methodphone_match \id_challenge \manual \
expires_atstring | nulldate-time
notestring | nullmá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

En esta página