VitrinaAPI

Prestaciones de la clínica

Qué hace la clínica: duración, precio de lista, requisitos de consentimiento y quién puede ejecutar cada prestación.

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.

Definir prestaciones y precios explica este recurso en prosa, con ejemplos ejecutables.

MétodoRutaQué hace
PUT/clinic/booking-catalogChoose what the agent and «Nueva cita» book against
GET/clinic/motivosList the clinic’s motivos de atención
PATCH/clinic/motivos/{id}Edit a motivo de atención
PUT/clinic/motivos/{id}/professionalsChoose who attends a motivo
POST/clinic/motivos/agent-bookableSet which motivos the AI agent may book, in bulk
POST/clinic/motivos/resyncResync motivos de atención from the clinic system
GET/clinic/service-categoriesList the clinic's service categories
POST/clinic/service-categoriesCreate a service category (find-or-create, native only)
DELETE/clinic/service-categories/{id}Delete a service category
PATCH/clinic/service-categories/{id}Enable/disable a service category
GET/clinic/servicesList the clinic's services
POST/clinic/servicesCreate a native service
DELETE/clinic/services/{id}Delete a native service
PATCH/clinic/services/{id}Edit a native service (or activar/desactivar)
GET/clinic/services/{id}/professionalsWho may perform a prestación
PUT/clinic/services/{id}/professionalsSet who may perform a prestación
POST/clinic/services/agent-bookableSet which prestaciones the AI agent may book, in bulk
GET/clinic/services/agent-bookable-countHow many active prestaciones the AI agent may book
GET/clinic/services/arancelesThe clinic's vendor aranceles (price lists)

PUT /clinic/booking-catalog

Choose what the agent and «Nueva cita» book against

motivos (the clinic system’s motivos de atención) or prestaciones (the price list). motivos needs at least one synced motivo.

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
catalogmotivos \prestacionessí
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/booking-catalog \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "catalog": "motivos"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "catalog": "motivos"
  }
}

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

GET /clinic/motivos

List the clinic’s motivos de atención

The «motivos de atención» mirrored from Dentalink/Medilink — the clinic system’s own bookable visit kinds per especialidad, each with its length and the professionals who attend it. vendor_* is what the clinic system said at the last resync; duration_minutes / active / the professional set are the effective values, which follow it unless overrides names them. booking_catalog says whether the AI agent and «Nueva cita» book against these or against the prestaciones.

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.

curl https://api.vitrinadev.com/api/v1/clinic/motivos \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "booking_catalog": "motivos",
    "product": "dentalink",
    "last_synced_at": "2026-10-22T18:00:00.000Z",
    "last_error": null,
    "motivos": [
      {
        "id": "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01",
        "tenant_id": "0194e7a0-0000-7000-8000-000000000001",
        "source": "healthatom",
        "external_id": "23",
        "nombre": "DIAGNOSTICO INTEGRAL ODONTOLOGIA",
        "especialidad": "General",
        "vendor_duration_minutes": 45,
        "duration_minutes": 60,
        "vendor_active": true,
        "active": true,
        "agent_bookable": true,
        "color": "#cccccc",
        "agenda_online": false,
        "overrides": [
          "duration_minutes"
        ],
        "synced_at": "2026-10-22T18:00:00.000Z",
        "created_at": "2026-10-22T18:00:00.000Z",
        "updated_at": "2026-10-22T18:05:00.000Z",
        "professionals": [
          {
            "external_id": "8",
            "nombre": "Catalina Lima",
            "vendor_duration_minutes": 45,
            "duration_minutes": 60,
            "active": true
          }
        ]
      }
    ]
  }
}

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

PATCH /clinic/motivos/{id}

Edit a motivo de atención

A duration_minutes or active set here becomes an override: every later resync keeps it (and reports it). A length set to exactly the clinic system’s own figure is not an override. reset hands fields back to the clinic system. A length set here applies to every professional; otherwise a booking uses the professional’s own length from the clinic system.

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
idpathuuidsí

Cuerpo

CampoTipoObligatorioRestricciones
duration_minutesinteger—≥ 5, ≤ 1440
activeboolean—
agent_bookableboolean—
resetduration_minutes \active \professionals[]
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/motivos/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "duration_minutes": 60
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01",
    "tenant_id": "0194e7a0-0000-7000-8000-000000000001",
    "source": "healthatom",
    "external_id": "23",
    "nombre": "DIAGNOSTICO INTEGRAL ODONTOLOGIA",
    "especialidad": "General",
    "vendor_duration_minutes": 45,
    "duration_minutes": 60,
    "vendor_active": true,
    "active": true,
    "agent_bookable": true,
    "color": "#cccccc",
    "agenda_online": false,
    "overrides": [
      "duration_minutes"
    ],
    "synced_at": "2026-10-22T18:00:00.000Z",
    "created_at": "2026-10-22T18:00:00.000Z",
    "updated_at": "2026-10-22T18:05:00.000Z",
    "professionals": [
      {
        "external_id": "8",
        "nombre": "Catalina Lima",
        "vendor_duration_minutes": 45,
        "duration_minutes": 60,
        "active": true
      }
    ]
  }
}

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

PUT /clinic/motivos/{id}/professionals

Choose who attends a motivo

Replaces the professionals who attend the motivo (by the clinic system’s professional id). An override: resyncs keep it until reset: ["professionals"].

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
idpathuuidsí

Cuerpo

CampoTipoObligatorioRestricciones
professionalsstring[]sí
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/motivos/<id>/professionals \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "professionals": [
      "8",
      "19"
    ]
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01",
    "tenant_id": "0194e7a0-0000-7000-8000-000000000001",
    "source": "healthatom",
    "external_id": "23",
    "nombre": "DIAGNOSTICO INTEGRAL ODONTOLOGIA",
    "especialidad": "General",
    "vendor_duration_minutes": 45,
    "duration_minutes": 60,
    "vendor_active": true,
    "active": true,
    "agent_bookable": true,
    "color": "#cccccc",
    "agenda_online": false,
    "overrides": [
      "duration_minutes"
    ],
    "synced_at": "2026-10-22T18:00:00.000Z",
    "created_at": "2026-10-22T18:00:00.000Z",
    "updated_at": "2026-10-22T18:05:00.000Z",
    "professionals": [
      {
        "external_id": "8",
        "nombre": "Catalina Lima",
        "vendor_duration_minutes": 45,
        "duration_minutes": 60,
        "active": true
      }
    ]
  }
}

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

POST /clinic/motivos/agent-bookable

Set which motivos the AI agent may book, in bulk

«¿Ofrecer servicio?» — Vitrina’s own flag, never sent to the clinic system and never touched by a resync.

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
valuebooleansí
idsuuid[]sí
curl -X POST https://api.vitrinadev.com/api/v1/clinic/motivos/agent-bookable \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "value": false,
    "ids": [
      "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01"
    ]
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "updated": 1
  }
}

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

POST /clinic/motivos/resync

Resync motivos de atención from the clinic system

Reads the clinic system now and merges it in: new motivos are added, the rest refresh, and every field the clinic changed in Vitrina (overrides) is KEPT and listed in kept with both values. Motivos the clinic system no longer lists land in removed (deactivated, not deleted). Synchronous — about one second per professional.

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.

curl -X POST https://api.vitrinadev.com/api/v1/clinic/motivos/resync \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "total": 32,
    "synced_at": "2026-10-22T18:00:00.000Z",
    "added": [
      {
        "id": null,
        "nombre": "Blanqueamiento",
        "especialidad": "General"
      }
    ],
    "updated": [],
    "removed": [],
    "kept": [
      {
        "id": "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01",
        "nombre": "DIAGNOSTICO INTEGRAL ODONTOLOGIA",
        "especialidad": "General",
        "field": "duration_minutes",
        "vendor": "45 min",
        "vitrina": "60 min"
      }
    ]
  }
}

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

GET /clinic/service-categories

List the clinic's service categories

List the clinic's service categories

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
enabledquerytrue \falseno
curl https://api.vitrinadev.com/api/v1/clinic/service-categories \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "795cf9e4-0000-4000-8000-000000000001",
      "source": "native",
      "nombre": "Ortodoncia",
      "enabled": true,
      "created_at": "2026-09-22T22:21:14.461Z",
      "updated_at": "2026-09-22T22:21:14.461Z"
    }
  ]
}

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

POST /clinic/service-categories

Create a service category (find-or-create, native only)

Create a service category (find-or-create, native only)

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
nombrestringsímín. 1, máx. 120
curl -X POST https://api.vitrinadev.com/api/v1/clinic/service-categories \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "nombre": "Ortodoncia"
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "795cf9e4-0000-4000-8000-000000000001",
    "source": "native",
    "nombre": "Ortodoncia",
    "enabled": true,
    "created_at": "2026-09-22T22:21:14.461Z",
    "updated_at": "2026-09-22T22:21:14.461Z"
  }
}

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

DELETE /clinic/service-categories/{id}

Delete a service category

Delete a service category

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
idpathuuidsí
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/service-categories/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

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

PATCH /clinic/service-categories/{id}

Enable/disable a service category

Enable/disable a service category

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
idpathuuidsí

Cuerpo

CampoTipoObligatorioRestricciones
enabledbooleansí
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/service-categories/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "795cf9e4-0000-4000-8000-000000000001",
    "source": "native",
    "nombre": "Ortodoncia",
    "enabled": true,
    "created_at": "2026-09-22T22:21:14.461Z",
    "updated_at": "2026-09-22T22:21:14.476Z"
  }
}

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

GET /clinic/services

List the clinic's services

The clinic's price list. search (min 2 chars) matches nombre, código or categoría accent-insensitively. active is OPTIONAL and omitting it returns every row. precio is the vendor LIST price before previsión/convenio — it is deliberately never shown to the AI agent. is_entry filters entry prestaciones, tri-state: true = entry (evaluación / diagnóstico), false = CONFIRMED treatment rows only, unclassified = nobody has said yet (is_entry IS NULL); omitted = every row. arancel (repeatable or comma-separated) keeps only rows priced in those vendor aranceles ('0' = «Arancel base»); each row carries arancel_external_id + arancel_name. categoria keeps one exact category. agent_bookable (yes | no) filters on the EFFECTIVE agent-bookable value: each row carries the explicit agent_bookable (null = undecided) and agent_bookable_effective (agent_bookable ?? settings.clinic_attendance.agent_bookable_default, unset default = true).

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
searchquerystringnomín. 2, máx. 120
activequerytrue \falseno
review_statequerypending \stale \draft \
is_entryquerytrue \false \unclassified
arancelqueryarray | nullno
categoriaquerystringnomín. 1, máx. 120
agent_bookablequeryyes \nono
sortquerynombre \codigo \categoria \
dirqueryasc \descno
pagequeryintegerno≥ 1, por defecto 1
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
curl https://api.vitrinadev.com/api/v1/clinic/services \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "data": [
      {
        "id": "4cf5bc59-0000-4000-8000-000000000001",
        "source": "native",
        "external_id": null,
        "nombre": "Control de ortodoncia",
        "codigo": "ORT-CTRL",
        "categoria": "Ortodoncia",
        "tipo": null,
        "precio": 25000,
        "arancel": null,
        "duration_minutes": 30,
        "vendor_duration_minutes": null,
        "review_state": "draft",
        "reviewed_at": null,
        "reviewed_by": null,
        "is_telehealth": false,
        "online_bookable": true,
        "requires_consent_template_id": null,
        "eligibility_min_age_years": null,
        "eligibility_max_age_years": null,
        "eligibility_sexo": null,
        "eligibility_previsiones": null,
        "active": true,
        "synced_at": null
      },
      {
        "id": "4cf5bc59-0000-4000-8000-000000000002",
        "source": "native",
        "external_id": null,
        "nombre": "Limpieza dental",
        "codigo": null,
        "categoria": "Ortodoncia",
        "tipo": null,
        "precio": 35000,
        "arancel": null,
        "duration_minutes": 45,
        "vendor_duration_minutes": null,
        "review_state": "draft",
        "reviewed_at": null,
        "reviewed_by": null,
        "is_telehealth": false,
        "online_bookable": false,
        "requires_consent_template_id": null,
        "eligibility_min_age_years": null,
        "eligibility_max_age_years": null,
        "eligibility_sexo": null,
        "eligibility_previsiones": null,
        "active": true,
        "synced_at": null
      }
    ],
    "total": 2,
    "page": 1,
    "limit": 50
  }
}

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

POST /clinic/services

Create a native service

Create a native service

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
nombrestringsímín. 1, máx. 200
codigostring | null—máx. 60
categoriastring | null—máx. 120
tipostring | null—máx. 120
preciointeger | null—≥ 0, ≤ 1000000000
duration_minutesinteger | null—≥ 1, ≤ 1440
activeboolean—
is_telehealthboolean—
online_bookableboolean—
is_entryboolean | null—
agent_bookableboolean | null—
lab_bearingboolean—
requires_consent_template_idstring | null—
eligibility_min_age_yearsinteger | null—≥ 0, ≤ 120
eligibility_max_age_yearsinteger | null—≥ 0, ≤ 120
eligibility_sexoF \M \otro
eligibility_previsionesarray | null—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/services \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "nombre": "Control de ortodoncia",
    "codigo": "ORT-CTRL",
    "categoria": "Ortodoncia",
    "precio": 25000,
    "duration_minutes": 30,
    "online_bookable": true
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "4cf5bc59-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "nombre": "Control de ortodoncia",
    "codigo": "ORT-CTRL",
    "categoria": "Ortodoncia",
    "tipo": null,
    "precio": 25000,
    "arancel": null,
    "duration_minutes": 30,
    "vendor_duration_minutes": null,
    "review_state": "draft",
    "reviewed_at": null,
    "reviewed_by": null,
    "is_telehealth": false,
    "online_bookable": true,
    "requires_consent_template_id": null,
    "eligibility_min_age_years": null,
    "eligibility_max_age_years": null,
    "eligibility_sexo": null,
    "eligibility_previsiones": null,
    "active": true,
    "synced_at": null
  }
}

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

DELETE /clinic/services/{id}

Delete a native service

Delete a native service

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
idpathuuidsí
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/services/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

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

PATCH /clinic/services/{id}

Edit a native service (or activar/desactivar)

Edit a native service (or activar/desactivar)

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
idpathuuidsí

Cuerpo

CampoTipoObligatorioRestricciones
nombrestring—mín. 1, máx. 200
codigostring | null—máx. 60
categoriastring | null—máx. 120
tipostring | null—máx. 120
preciointeger | null—≥ 0, ≤ 1000000000
duration_minutesinteger | null—≥ 1, ≤ 1440
activeboolean—
is_telehealthboolean—
online_bookableboolean—
is_entryboolean | null—
agent_bookableboolean | null—
lab_bearingboolean—
requires_consent_template_idstring | null—
eligibility_min_age_yearsinteger | null—≥ 0, ≤ 120
eligibility_max_age_yearsinteger | null—≥ 0, ≤ 120
eligibility_sexoF \M \otro
eligibility_previsionesarray | null—
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/services/<id> \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "precio": 28000
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "4cf5bc59-0000-4000-8000-000000000001",
    "source": "native",
    "external_id": null,
    "nombre": "Control de ortodoncia",
    "codigo": "ORT-CTRL",
    "categoria": "Ortodoncia",
    "tipo": null,
    "precio": 28000,
    "arancel": null,
    "duration_minutes": 30,
    "vendor_duration_minutes": null,
    "review_state": "draft",
    "reviewed_at": null,
    "reviewed_by": null,
    "is_telehealth": false,
    "online_bookable": true,
    "requires_consent_template_id": null,
    "eligibility_min_age_years": null,
    "eligibility_max_age_years": null,
    "eligibility_sexo": null,
    "eligibility_previsiones": null,
    "active": true,
    "synced_at": null
  }
}

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

GET /clinic/services/{id}/professionals

Who may perform a prestación

The eligibility set, with each professional’s optional duration and price override. NO ROWS means "anyone may" — never "nobody may".

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.

curl https://api.vitrinadev.com/api/v1/clinic/services/<id>/professionals \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "18342d1b-0000-4000-8000-000000000002",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
      "professional_id": "18342d1b-0000-4000-8000-000000000001",
      "duration_minutes": 30,
      "precio_clp": 28000,
      "active": true,
      "created_at": "2026-09-22T22:21:14.591Z",
      "updated_at": "2026-09-22T22:21:14.591Z",
      "professional_nombre": "Ana"
    }
  ]
}

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

PUT /clinic/services/{id}/professionals

Set who may perform a prestación

Replaces the WHOLE set in one transaction. An empty array clears it, which means "anyone may". 409 duplicate_professional when the body names the same professional twice.

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
professionalsobject[]sí
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/services/<id>/professionals \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "professionals": [
      {
        "professional_id": "18342d1b-0000-4000-8000-000000000001",
        "duration_minutes": 30,
        "precio_clp": 28000
      }
    ]
  }'

Ejemplo de respuesta (200)

{
  "data": [
    {
      "id": "18342d1b-0000-4000-8000-000000000002",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
      "professional_id": "18342d1b-0000-4000-8000-000000000001",
      "duration_minutes": 30,
      "precio_clp": 28000,
      "active": true,
      "created_at": "2026-09-22T22:21:14.591Z",
      "updated_at": "2026-09-22T22:21:14.591Z",
      "professional_nombre": "Ana"
    }
  ]
}

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

POST /clinic/services/agent-bookable

Set which prestaciones the AI agent may book, in bulk

Sets agent_bookable to value — true (the agent may offer and book it), false (reception-only: the agent knows it exists, tells the patient reception schedules it and never books it) or null (back to the tenant default) — on up to 500 ids, OR on every row matching all_matching (the same filters, in the same string form, as the GET /clinic/services query). Exactly one of the two. Works on ANY source — the flag is Vitrina-owned and never reaches the vendor. updated is how many rows matched; ids of another tenant match nothing.

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
valueboolean | nullsí
idsuuid[]—
all_matchingClinicServiceMatchFilter—
curl -X POST https://api.vitrinadev.com/api/v1/clinic/services/agent-bookable \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "value": false,
    "all_matching": {
      "search": "control",
      "active": "true",
      "arancel": "0"
    }
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "updated": 37
  }
}

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

GET /clinic/services/agent-bookable-count

How many active prestaciones the AI agent may book

\{ bookable, reception_only, total \} over ACTIVE services, by the EFFECTIVE agent-bookable value (agent_bookable, or the tenant default settings.clinic_attendance.agent_bookable_default when the row is undecided). arancel (repeatable or comma-separated) counts only rows priced in those vendor aranceles.

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
arancelqueryarray | nullno
curl https://api.vitrinadev.com/api/v1/clinic/services/agent-bookable-count \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "bookable": 12,
    "reception_only": 383,
    "total": 395
  }
}

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

GET /clinic/services/aranceles

The clinic's vendor aranceles (price lists)

One entry per distinct arancel_external_id in the mirrored catálogo, with total rows and active rows, largest active list first. Dentalink/Medilink keep one copy of every prestación per arancel; this is what the screen filters on and what tenant.settings.clinic_catalog_aranceles (the aranceles the agent quotes) is chosen from. Empty for Reservo / native catálogos.

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.

curl https://api.vitrinadev.com/api/v1/clinic/services/aranceles \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": [
    {
      "external_id": "0",
      "name": "Arancel base",
      "total": 412,
      "active": 390
    },
    {
      "external_id": "652",
      "name": "Arancel convenio",
      "total": 652,
      "active": 640
    }
  ]
}

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

En esta página