VitrinaAPI

Agenda de la clínica

El día de una clínica: leer la agenda, buscar horas libres, reservar y mover una cita, con los mismos ids sea cual sea el sistema que la lleva. Una aplicación conectada la lee con seudónimos de paciente.

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.

Reservar una hora en la clínica explica este recurso en prosa, con ejemplos ejecutables.

MétodoRutaQué hace
POST/clinic/agenda/appointmentsBook a cita in the diary Vitrina owns
PATCH/clinic/agenda/appointments/{id}Move a cita
GET/clinic/agenda/availabilityFree slots, from whichever engine owns this diary
GET/clinic/agenda/feedAgenda for a window, any engine, by professional and branch

POST /clinic/agenda/appointments

Book a cita in the diary Vitrina owns

Routes through the tenant's booking engine, so a vendor clinic gets its own engine's honest refusal rather than a local row the vendor never hears about. Pass either the slot_ref from GET /clinic/agenda/availability or a professional_id outright (a drag-to-create on the grid has no ref). One transaction: the cita, its prestación line with the duration and price FROZEN, and — when a deposit policy applies — the customer_obligation the ledger settles against. A collision is refused with slot_taken and fresh alternatives unless allow_overbook is set, which also requires a reason: a sobrecupo nobody can explain is indistinguishable from a double booking. Requires clinic:write, which no connected app holds by default.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

Cuerpo

CampoTipoObligatorioRestricciones
slot_refstringmín. 1
professional_iduuid
resource_iduuid
location_iduuid
service_iduuid
patient_iduuid
contact_iduuid
conversation_iduuid
customer_namestringmín. 1, máx. 200
starts_atstring
ends_atstring
notesstringmáx. 2000
allow_overbookboolean
additionalboolean
overbook_reasonstringmín. 1, máx. 300
curl -X POST https://api.vitrinadev.com/api/v1/clinic/agenda/appointments \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "professional_id": "ffffffff-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "patient_id": "12121212-0000-4000-8000-000000000001",
    "starts_at": "2026-09-24T14:00:00.000Z",
    "ends_at": "2026-09-24T14:30:00.000Z",
    "notes": "Control mensual"
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "eeeeeeee-0000-4000-8000-000000000002",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "lead_id": null,
    "contact_id": null,
    "conversation_id": null,
    "vehicle_id": null,
    "appointment_type_id": null,
    "owner_user_id": null,
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "professional_id": "ffffffff-0000-4000-8000-000000000001",
    "resource_id": null,
    "kind": "clinic",
    "source": "agent",
    "engine": "native",
    "external_id": null,
    "status": "confirmed",
    "status_epoch": 0,
    "clinic_status_id": null,
    "arrival_state": "scheduled",
    "arrived_at": null,
    "in_room_at": null,
    "done_at": null,
    "series_id": null,
    "series_index": null,
    "booking_landing_id": null,
    "starts_at": "2026-09-24T14:00:00.000Z",
    "ends_at": "2026-09-24T14:30:00.000Z",
    "hold_expires_at": null,
    "google_event_id": null,
    "google_ical_uid": null,
    "google_updated_at": null,
    "google_etag": null,
    "reminder_at": null,
    "reminder_sent_at": null,
    "reminder_job_id": null,
    "customer_name": "María José Fuentes Lagos",
    "notes": "Control mensual",
    "metadata": {
      "engine": "native-clinic",
      "patient_rut": "11.111.111-1",
      "clinic_patient_id": "12121212-0000-4000-8000-000000000001"
    },
    "display_seq": 2,
    "display_id": "A-2",
    "created_at": "2026-09-22T17:18:58.202Z",
    "updated_at": "2026-09-22T17:18:58.202Z"
  }
}

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

PATCH /clinic/agenda/appointments/{id}

Move a cita

Reschedule through the owning engine — a native cita moves inside one transaction under the same per-professional lock a booking takes, and a mirrored one moves the way its vendor moves citas. The deposit's deadline travels with it: a cita moved a week out with a deadline that stayed put is a "vencido" nobody can date. professional_id / resource_id move the cita to another agenda or room in the same stroke; omitted, it keeps the ones it has. Requires clinic:write.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParámetroEnTipoObligatorioRestricciones
idpathuuid

Cuerpo

CampoTipoObligatorioRestricciones
slot_refstringmín. 1
professional_iduuid
resource_iduuid
starts_atstring
ends_atstring
allow_overbookboolean
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/agenda/appointments/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "starts_at": "2026-09-24T14:30:00.000Z",
    "ends_at": "2026-09-24T15:00:00.000Z"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "eeeeeeee-0000-4000-8000-000000000002",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "lead_id": null,
    "contact_id": null,
    "conversation_id": null,
    "vehicle_id": null,
    "appointment_type_id": null,
    "owner_user_id": null,
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "professional_id": "ffffffff-0000-4000-8000-000000000001",
    "resource_id": null,
    "kind": "clinic",
    "source": "agent",
    "engine": "native",
    "external_id": null,
    "status": "confirmed",
    "status_epoch": 0,
    "clinic_status_id": null,
    "arrival_state": "scheduled",
    "arrived_at": null,
    "in_room_at": null,
    "done_at": null,
    "series_id": null,
    "series_index": null,
    "booking_landing_id": null,
    "starts_at": "2026-09-24T14:30:00.000Z",
    "ends_at": "2026-09-24T15:00:00.000Z",
    "hold_expires_at": null,
    "google_event_id": null,
    "google_ical_uid": null,
    "google_updated_at": null,
    "google_etag": null,
    "reminder_at": null,
    "reminder_sent_at": null,
    "reminder_job_id": null,
    "customer_name": "María José Fuentes Lagos",
    "notes": "Control mensual",
    "metadata": {
      "engine": "native-clinic",
      "patient_rut": "11.111.111-1",
      "clinic_patient_id": "12121212-0000-4000-8000-000000000001"
    },
    "display_seq": 2,
    "display_id": "A-2",
    "created_at": "2026-09-22T17:18:58.202Z",
    "updated_at": "2026-09-22T17:19:15.981Z"
  }
}

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

GET /clinic/agenda/availability

Free slots, from whichever engine owns this diary

One endpoint, three truths: a Medilink clinic gets its vendor's live answer, a Reservo clinic the local compute over its mirror, and a native clinic the professionals' work patterns minus exceptions, existing citas and room contention. Each slot carries an opaque slot_ref that pins the professional, the box and the branch — book it back verbatim. allow_overbook is a STAFF flag and defaults to false: an hour that only exists because the clinic permits sobrecupo is a receptionist's decision with a reason, never something a patient-facing read gives away.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParámetroEnTipoObligatorioRestricciones
service_idqueryuuidno
professional_idqueryuuidno
location_idqueryuuidno
especialidadquerystringnomín. 1, máx. 120
fromquerystringno
toquerystringno
limitqueryintegerno≥ 1, ≤ 100
allow_overbookqueryboolean | nullno
curl https://api.vitrinadev.com/api/v1/clinic/agenda/availability \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "engine": "native-clinic",
    "timezone": "America/Santiago",
    "slots": [
      {
        "starts_at": "2026-09-24T12:00:00.000Z",
        "ends_at": "2026-09-24T12:30:00.000Z",
        "label": "jueves, 24 de septiembre, 09:00",
        "slot_ref": "ncl1_eyJwIjoiZmZmZmZmZmYtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAxIiwibCI6ImIxYjFiMWIxLTAwMDAtNDAwMC04MDAwLTAwMDAwMDAwMDAwMiIsInMiOiIyMDI2LTA5LTI0VDEyOjAwOjAwLjAwMFoiLCJlIjoiMjAyNi0wOS0yNFQxMjozMDowMC4wMDBaIn0",
        "professional_id": "ffffffff-0000-4000-8000-000000000001",
        "professional_name": "Ana Rojas"
      },
      {
        "starts_at": "2026-09-24T12:30:00.000Z",
        "ends_at": "2026-09-24T13:00:00.000Z",
        "label": "jueves, 24 de septiembre, 09:30",
        "slot_ref": "ncl1_eyJwIjoiZmZmZmZmZmYtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAxIiwibCI6ImIxYjFiMWIxLTAwMDAtNDAwMC04MDAwLTAwMDAwMDAwMDAwMiIsInMiOiIyMDI2LTA5LTI0VDEyOjMwOjAwLjAwMFoiLCJlIjoiMjAyNi0wOS0yNFQxMzowMDowMC4wMDBaIn0",
        "professional_id": "ffffffff-0000-4000-8000-000000000001",
        "professional_name": "Ana Rojas"
      }
    ],
    "searched_through": "2026-09-24",
    "truncated": false
  }
}

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

GET /clinic/agenda/feed

Agenda for a window, any engine, by professional and branch

Mirrored AND native appointments between from (inclusive) and to (EXCLUSIVE), bare YYYY-MM-DD days in the clinic's timezone; the window may not exceed 8 days. Cancelled citas are INCLUDED (the grid draws them muted). Unlike GET /clinic/agenda, professional_id and location_id are VITRINA uuids — clinic_professional.id and location.id — so one query serves a Medilink, a Reservo and a native clinic. Each row still carries the vendor's own ids under vendor for provenance: a row with vendor.professional_id set and professional: null is a roster gap, never a cita nobody is attending. Never calls the clinic API. flags are the clinical alerts the clinic shows on the block: clinical-record content, returned to an API key or personal token only when it also holds clinic_record:read.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParámetroEnTipoObligatorioRestricciones
fromquerystringpatrón ^\d{4}-\d{2}-\d{2}$
toquerystringpatrón ^\d{4}-\d{2}-\d{2}$
professional_idqueryuuidno
location_idqueryuuidno
resource_idqueryuuidno
statusquerypending_hold \confirmed \cancelled \
kindquerytest_drive \external \block \
curl https://api.vitrinadev.com/api/v1/clinic/agenda/feed \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "eeeeeeee-0000-4000-8000-000000000001",
      "display_id": "A-1",
      "engine": "native",
      "external_id": null,
      "kind": "clinic",
      "status": "confirmed",
      "clinic_status_id": null,
      "status_label": null,
      "status_color": null,
      "starts_at": "2026-09-23T13:30:00.000Z",
      "ends_at": "2026-09-23T14:00:00.000Z",
      "arrival_state": "scheduled",
      "professional": {
        "id": "ffffffff-0000-4000-8000-000000000001",
        "name": "Ana"
      },
      "location": {
        "id": "b1b1b1b1-0000-4000-8000-000000000002",
        "name": "Sucursal Maipú"
      },
      "resource": null,
      "service": null,
      "patient": {
        "id": "12121212-0000-4000-8000-000000000001",
        "name": "María José Fuentes Lagos",
        "external_id": null
      },
      "contact": {
        "id": "22222222-0000-4000-8000-000000000001",
        "name": "María José Fuentes Lagos"
      },
      "customer_name": "María José Fuentes Lagos",
      "notes": "Avisar al +56987654321 si se atrasa",
      "money": null,
      "flags": [
        {
          "id": "13131313-0000-4000-8000-000000000001",
          "kind": "alergia",
          "label": "Alergia a penicilina",
          "severity": "severa"
        }
      ],
      "vendor": {
        "professional_id": null,
        "professional_name": null,
        "sucursal_id": null,
        "agenda_id": null,
        "sillon": null,
        "status_code": null
      }
    }
  ]
}

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

En esta página