VitrinaAPI

Abrir la agenda de la clínica a tu software

Lectura y escritura de la agenda para tu software, sin tocar la ficha.

ClínicasSolo en los workspaces de clínicas.

El software de gestión de la clínica ya existe, y ya tiene su propia agenda. Esta receta lo conecta a la de Vitrina, para leer y reservar horas sin que nadie retipee nada a mano. Al final, tu sistema reserva, mueve y consulta citas directo, y nunca ve una ficha.

Trampa

Una key sk_ recibe el nombre del paciente

Una key con clinic:read recibe el nombre de quien tiene la hora. El seudónimo, del tipo «J.P. · #4821», solo se aplica a una aplicación conectada por OAuth. La ficha (antecedentes, notas clínicas, alertas de la agenda) no llega sin clinic_record:read.

Antes de empezar

  • Un tenant de tipo clínica, con profesionales y prestaciones ya cargados (Catálogo clínico tiene el orden).
  • Una key con clinic:read y clinic:write, y nada más. Sin clinic_patients:read ni clinic_record:read: la agenda no los pide, y agregarlos amplía lo que expone una key filtrada.
  • Si la clínica corre sobre Dentalink, Medilink o Reservo, el catálogo se edita ahí. Vitrina se limita a reflejarlo.

1. Resuelve a quién y a qué corresponde cada hora

La agenda solo trae ids. Antes de mostrar algo legible, tu sistema necesita el catálogo detrás:

curl "https://api.vitrinadev.com/api/v1/clinic/professionals?limit=1" \
  -H "Authorization: Bearer $VITRINA_KEY"
{
  "data": {
    "data": [
      {
        "id": "63f237cc-8503-42b4-ad56-b884431be966",
        "source": "native",
        "nombre": "Benjamín",
        "apellidos": "Pinilla Irarrázabal",
        "especialidad": "Ortodoncia",
        "agenda_online": true,
        "intervalo_minutes": 15,
        "active": true
      }
    ],
    "total": 5,
    "page": 1,
    "limit": 1
  }
}

source: "native" importa tanto como el nombre. Marca que este profesional se administra en Vitrina. Si dijera el nombre de un proveedor, editarlo por esta vía respondería 409, ver más abajo.

2. Lee la agenda de una ventana

curl "https://api.vitrinadev.com/api/v1/clinic/agenda/feed?from=2026-09-21&to=2026-09-23" \
  -H "Authorization: Bearer $VITRINA_KEY"
{
  "data": [
    {
      "id": "4c5a9b18-e565-45c8-8472-93e81454d5e7",
      "display_id": "A-4270",
      "engine": "native",
      "kind": "clinic",
      "status": "completed",
      "starts_at": "2026-09-21T12:00:00.000Z",
      "ends_at": "2026-09-21T12:15:00.000Z",
      "professional": { "id": "e0782bc5-c483-4d9d-99b2-76515de2a2f0", "name": "Magdalena" },
      "location": { "id": "5d091787-f540-4869-b4d0-50f3b5402b74", "name": "Aurora Providencia" },
      "service": { "id": "546db704-ea02-4066-9d6e-9f0f2c1dc809", "name": "Radiografía periapical" },
      "patient": { "id": "62bcf8c9-f2d4-4191-99a3-bdab357e5952", "name": "Constanza Alcaíno Yáñez", "external_id": null },
      "notes": null
    }
  ]
}

El nombre del paciente viaja completo. flags no aparece en la respuesta: solo llega a una key con clinic_record:read, con las alertas clínicas del bloque.

3. Encuentra un hueco y resérvalo

curl "https://api.vitrinadev.com/api/v1/clinic/agenda/availability?service_id=0c1e0c10-6d1c-4ff9-a4a6-e1109a46e507&from=2026-09-28&to=2026-09-30&limit=1" \
  -H "Authorization: Bearer $VITRINA_KEY"
{
  "data": {
    "engine": "native-clinic",
    "timezone": "America/Santiago",
    "slots": [
      {
        "starts_at": "2026-09-28T13:00:00.000Z",
        "ends_at": "2026-09-28T13:40:00.000Z",
        "label": "lunes, 28 de septiembre, 10:00",
        "slot_ref": "ncl1_eyJwIjoiNjNmMjM3Y2MtODUwMy00MmI0LWFkNTYtYjg4NDQzMWJlOTY2IiwiciI6IjkxMDAwNjJmLTFjYmYtNGIyOC05M2M5LWU0Y2E5NDg0NDNjMSIsImwiOiI1ZDA5MTc4Ny1mNTQwLTQ4NjktYjRkMC01MGYzYjU0MDJiNzQiLCJzIjoiMjAyNi0wOS0yOFQxMzowMDowMC4wMDBaIiwiZSI6IjIwMjYtMDktMjhUMTM6NDA6MDAuMDAwWiJ9",
        "professional_id": "63f237cc-8503-42b4-ad56-b884431be966",
        "professional_name": "Benjamín Pinilla Irarrázabal"
      }
    ],
    "searched_through": "2026-09-28",
    "truncated": true
  }
}

slot_ref codifica el profesional, el box y el horario en un solo token opaco. No lo armas a mano: lo pasas de vuelta tal cual, junto con starts_at y ends_at, que el cuerpo pide igual aunque vengan repetidos en el token.

curl -X POST https://api.vitrinadev.com/api/v1/clinic/agenda/appointments \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slot_ref": "ncl1_eyJwIjoiNjNmMjM3Y2MtODUwMy00MmI0LWFkNTYtYjg4NDQzMWJlOTY2IiwiciI6IjkxMDAwNjJmLTFjYmYtNGIyOC05M2M5LWU0Y2E5NDg0NDNjMSIsImwiOiI1ZDA5MTc4Ny1mNTQwLTQ4NjktYjRkMC01MGYzYjU0MDJiNzQiLCJzIjoiMjAyNi0wOS0yOFQxMzowMDowMC4wMDBaIiwiZSI6IjIwMjYtMDktMjhUMTM6NDA6MDAuMDAwWiJ9",
    "service_id": "0c1e0c10-6d1c-4ff9-a4a6-e1109a46e507",
    "customer_name": "Prueba PMS externo",
    "starts_at": "2026-09-28T13:00:00.000Z",
    "ends_at": "2026-09-28T13:40:00.000Z"
  }'
{
  "data": {
    "id": "bacf8d6f-2076-4dcc-8339-771bcffe8872",
    "display_id": "A-4301",
    "professional_id": "63f237cc-8503-42b4-ad56-b884431be966",
    "resource_id": "9100062f-1cbf-4b28-93c9-e4ca948443c1",
    "location_id": "5d091787-f540-4869-b4d0-50f3b5402b74",
    "kind": "clinic",
    "engine": "native",
    "status": "confirmed",
    "starts_at": "2026-09-28T13:00:00.000Z",
    "ends_at": "2026-09-28T13:40:00.000Z",
    "customer_name": "Prueba PMS externo",
    "notes": null,
    "metadata": { "engine": "native-clinic", "treatment_name": "Exodoncia simple", "clinic_service_id": "0c1e0c10-6d1c-4ff9-a4a6-e1109a46e507" }
  }
}

A-4301, confirmada de inmediato: no hay un paso de espera aparte. resource_id llegó solo, resuelto del mismo slot_ref, así que no hace falta pedirle el box al usuario.

4. Muévela

curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/agenda/appointments/bacf8d6f-2076-4dcc-8339-771bcffe8872 \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "professional_id": "63f237cc-8503-42b4-ad56-b884431be966",
    "resource_id": "9100062f-1cbf-4b28-93c9-e4ca948443c1",
    "starts_at": "2026-09-28T14:00:00.000Z",
    "ends_at": "2026-09-28T14:40:00.000Z"
  }'
{
  "data": {
    "id": "bacf8d6f-2076-4dcc-8339-771bcffe8872",
    "display_id": "A-4301",
    "status": "confirmed",
    "starts_at": "2026-09-28T14:00:00.000Z",
    "ends_at": "2026-09-28T14:40:00.000Z",
    "updated_at": "2026-09-23T11:58:10.586Z"
  }
}

Mismo id, mismo display_id: es la misma cita, movida. professional_id y resource_id van otra vez completos, para el caso en que tu sistema también quiera cambiar quién atiende junto con la hora.

Cuando falla

Esta misma key, con solo clinic:read y clinic:write, no puede leer el registro de pacientes:

curl "https://api.vitrinadev.com/api/v1/clinic/patients?limit=2" \
  -H "Authorization: Bearer $VITRINA_KEY"
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Missing required scope: clinic_patients:read",
    "requestId": "5ed678e2-d31c-4a6b-b976-81e9d3f91757"
  }
}

Si la clínica corre sobre Dentalink, Medilink o Reservo, hay un candado aparte. Crear o editar un profesional, una prestación o un paciente por esta vía responde 409, con el motivo not_native_source: ese recurso se administra en el proveedor. La agenda no tiene ese candado. Reservar y mover citas sigue funcionando igual, porque una cita se escribe hacia el proveedor, no hacia su catálogo.

En la aplicación: la misma agenda, con el mismo catálogo, se ve desde Agenda dentro del workspace de la clínica. Guía completa en Manual → Agenda de la clínica.

Llevar las citas a tu sistema avisa de cada reserva, cada movimiento y cada cancelación, sin que tu sistema pregunte cada cinco minutos. Conectar Claude a tu agenda deja que un cliente de IA pregunte por la semana, sin escribir nada.

En esta página