Clinic agenda
A clinic's day: read the agenda, find free slots, book and move an appointment, with the same ids whichever system runs it. A connected app reads it with patient pseudonyms.
Download the full API pública projection: openapi.json.
Book into the clinic's agenda explains this resource in prose, with runnable examples.
| Method | Path | What it does |
|---|---|---|
POST | /clinic/agenda/appointments | Book a cita in the diary Vitrina owns |
PATCH | /clinic/agenda/appointments/{id} | Move a cita |
GET | /clinic/agenda/availability | Free slots, from whichever engine owns this diary |
GET | /clinic/agenda/feed | Agenda 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.
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
slot_ref | string | — | mín. 1 |
professional_id | uuid | — | |
resource_id | uuid | — | |
location_id | uuid | — | |
service_id | uuid | — | |
patient_id | uuid | — | |
contact_id | uuid | — | |
conversation_id | uuid | — | |
customer_name | string | — | mín. 1, máx. 200 |
starts_at | string | yes | |
ends_at | string | yes | |
notes | string | — | máx. 2000 |
allow_overbook | boolean | — | |
additional | boolean | — | |
overbook_reason | string | — | mí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"
}'Example response (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"
}
}Answers: 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.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
id | path | uuid | yes |
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
slot_ref | string | — | mín. 1 |
professional_id | uuid | — | |
resource_id | uuid | — | |
starts_at | string | yes | |
ends_at | string | yes | |
allow_overbook | boolean | — |
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"
}'Example response (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"
}
}Answers: 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.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
service_id | query | uuid | no | |
professional_id | query | uuid | no | |
location_id | query | uuid | no | |
especialidad | query | string | no | mín. 1, máx. 120 |
from | query | string | no | |
to | query | string | no | |
limit | query | integer | no | ≥ 1, ≤ 100 |
allow_overbook | query | boolean | null | no |
curl https://api.vitrinadev.com/api/v1/clinic/agenda/availability \
-H "Authorization: Bearer $VITRINA_KEY"Example response (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
}
}Answers: 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.
| Parameter | In | Type | Required | Constraints |
|---|---|---|---|---|
from | query | string | yes | patrón ^\d{4}-\d{2}-\d{2}$ |
to | query | string | yes | patrón ^\d{4}-\d{2}-\d{2}$ |
professional_id | query | uuid | no | |
location_id | query | uuid | no | |
resource_id | query | uuid | no | |
status | query | pending_hold \ | confirmed \ | cancelled \ |
kind | query | test_drive \ | external \ | block \ |
curl https://api.vitrinadev.com/api/v1/clinic/agenda/feed \
-H "Authorization: Bearer $VITRINA_KEY"Example response (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
}
}
]
}Answers: 200 · 400 · 401 · 403 · 404 · 409 · 429