En esta sección
Guías / Agenda y reservas

Agenda y reservas

Consulta los huecos libres de un servicio, agenda citas para tus contactos, muévelas o cancélalas, y entérate de cada cambio por webhook.

Actualizada el Ver .md

La agenda de Met es una sola: la que ve el negocio en el panel, la que usa el Met cuando agenda por WhatsApp y la que lees y escribes por la API. Una cita que creas aquí aparece en la pantalla de Agenda, se lleva al calendario conectado (Google u Outlook) y, si la ficha del contacto tiene correo, le llega la invitación.

El recorrido es siempre el mismo: qué se puede agendar (servicios) → cuándo hay espacio (huecos libres) → la cita.

Antes de empezar

ScopePara quéCon una key de test
agenda:readServicios, recursos, huecos libres y las citas (lista y detalle). También los eventos booking.*.Sí
agenda:writeCrear, reprogramar y cancelar citas.No: responde 403 test_mode_restricted, porque escribe en el calendario conectado y le manda la invitación al contacto.

Las rutas llevan el workspace: /workspaces/{workspaceId}/agenda/…. Con el SDK lo pasas una vez al crear el cliente. Los servicios, los recursos y el horario de atención se configuran en el panel (Agenda); por la API se leen.

import { Met } from '@meteor.ia/sdk';

const met = new Met(process.env.METEOR_API_KEY!, { workspaceId: 42 });
import os
from meteor_ia import Met

met = Met(os.environ["METEOR_API_KEY"], workspace_id=42)

1. Qué se puede agendar

const { data: servicios } = await met.agenda.eventTypes();
// [{ id: 3, object: 'event_type', name: 'Consulta inicial', duration_min: 30, modality: 'both', price: 80000, resource_ids: [5] }]

const { data: recursos } = await met.agenda.resources();
// [{ id: 5, object: 'agenda_resource', name: 'Dra. Ruiz', kind: 'staff', timezone: 'America/Bogota' }]
servicios = met.agenda.event_types()["data"]
recursos = met.agenda.resources()["data"]

Un servicio (event_type) es lo que se reserva: dura duration_min minutos, se atiende in_person, virtual o both, y lo atienden los recursos de resource_ids. Un recurso es quien o donde se atiende: una persona (staff), una sede (location) o un equipo (asset). Solo salen los activos, hasta 200 de cada uno.

2. Los huecos libres

Pide los huecos de un servicio entre dos fechas locales del negocio (YYYY-MM-DD), hasta 31 días por llamada:

const { data: huecos, has_more } = await met.agenda.slots({
  eventTypeId: 3,
  from: '2026-11-03',
  to: '2026-11-07',
});
// [{ object: 'slot', resource_id: 5, resource_name: 'Dra. Ruiz', starts_at: '2026-11-03T14:00:00.000Z',
//    ends_at: '2026-11-03T14:30:00.000Z', label: 'martes 3 de noviembre de 2026, 09:00 a. m.' }]
huecos = met.agenda.slots(event_type_id=3, from_="2026-11-03", to="2026-11-07")["data"]
curl "https://api.met.meteor.com.co/api/v1/workspaces/42/agenda/slots?event_type_id=3&from=2026-11-03&to=2026-11-07" \
  -H "Authorization: Bearer $METEOR_API_KEY"

Un hueco ya descuenta el horario de atención, las excepciones, los bloqueos, las citas tomadas y lo ocupado en el calendario conectado. label es la fecha escrita para mostrarle a una persona. Llegan hasta 200: si has_more es true, pide un rango más corto. Con resourceId (resource_id) ves solo los de un recurso.

En Python el parámetro se llama from_ porque from es palabra reservada; por la red viaja como from.

3. Agendar la cita

Pasa el starts_at y el resource_id de un hueco tal cual llegaron:

const cita = await met.agenda.bookings.create({
  eventTypeId: 3,
  contactId: 4821,
  startsAt: huecos[0].starts_at,
  resourceId: huecos[0].resource_id,
  modality: 'virtual',
  notes: 'Primera consulta, viene por recomendación.',
});
// { id: 91, object: 'booking', status: 'confirmed', video_call_url: 'https://meet.google.com/…', calendar_synced: true, … }
cita = met.agenda.bookings.create(
    event_type_id=3,
    contact_id=4821,
    starts_at=huecos[0]["starts_at"],
    resource_id=huecos[0]["resource_id"],
)
curl -X POST https://api.met.meteor.com.co/api/v1/workspaces/42/agenda/bookings \
  -H "Authorization: Bearer $METEOR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"event_type_id":3,"contact_id":4821,"starts_at":"2026-11-03T14:00:00.000Z","resource_id":5}'

calendar_synced dice si la cita quedó en el calendario conectado. video_call_url trae el enlace de la videollamada cuando el calendario lo genera.

Leer las citas

// Las próximas de un contacto
const { data, has_more } = await met.agenda.bookings.list({
  from: new Date().toISOString(),
  contactId: 4821,
  status: 'confirmed',
});

// Todas las de noviembre, sin manejar cursores
for await (const c of met.agenda.bookings.iterate({ from: '2026-11-01T00:00:00-05:00', to: '2026-11-30T23:59:59-05:00' })) {
  console.log(c.id, c.starts_at, c.status);
}

const una = await met.agenda.bookings.retrieve(91);
from datetime import datetime, timezone

proximas = met.agenda.bookings.list(from_=datetime.now(timezone.utc).isoformat(), contact_id=4821)
for c in met.agenda.bookings.iterate(from_="2026-11-01T00:00:00-05:00", to="2026-11-30T23:59:59-05:00"):
    print(c["id"], c["starts_at"], c["status"])

La lista va de la más antigua a la más reciente por hora de inicio. Sin from empieza por las que ya pasaron: para ver solo las próximas, pasa from con la hora actual. from y to son instantes ISO 8601 con zona (a diferencia de los huecos, que usan fechas locales). Filtra también por resourceId y por status: confirmed, cancelled, rescheduled, no_show o completed. Pagina con limit (1–100, 20 por defecto) y startingAfter = el id de la última cita de la página.

Reprogramar y cancelar

await met.agenda.bookings.reschedule(91, { startsAt: '2026-11-04T15:00:00.000Z' });
await met.agenda.bookings.cancel(91, { reason: 'El cliente viaja esa semana' });
met.agenda.bookings.reschedule(91, starts_at="2026-11-04T15:00:00.000Z")
met.agenda.bookings.cancel(91, reason="El cliente viaja esa semana")

Enterarte de cada cambio

Las citas cambian desde cuatro lugares: el Met en una conversación, una persona en el panel, la app operativa del negocio y tu integración. Los cuatro emiten los mismos eventos (ver Webhooks):

EventoCuándo
booking.createdSe agendó una cita.
booking.updatedSe reprogramó o se editó su detalle (modalidad, duración, sede, notas).
booking.cancelledSe canceló.
await met.webhooks.subscriptions.create({
  url: 'https://tu-sistema.com/met/eventos',
  enabled_events: ['booking.created', 'booking.updated', 'booking.cancelled'],
});

El data de los tres es la cita, con la misma forma de bookings.retrieve(), más el app_url que abre la conversación del contacto en Met. booking.updated trae además previous_attributes: solo los campos que cambiaron, con su valor de antes.

{
  "type": "booking.updated",
  "data": {
    "id": 91, "object": "booking", "status": "confirmed", "contact_id": 4821,
    "starts_at": "2026-11-04T15:00:00.000Z", "ends_at": "2026-11-04T15:30:00.000Z",
    "previous_attributes": { "starts_at": "2026-11-03T14:00:00.000Z", "ends_at": "2026-11-03T14:30:00.000Z" },
    "app_url": "https://met.meteor.com.co/abrir/42/contacto/4821"
  }
}

Guardar una cita sin cambiar nada no emite evento, y cancelar una que ya estaba cancelada tampoco. El evento sale con livemode: false solo cuando la cita la agendó un Met dentro de un Run de una key de test.

Desde un asistente (MCP)

El servidor MCP de Meteor tiene met_list_event_types, met_find_slots y met_list_bookings, que piden agenda:read. Las que escriben —met_create_booking, met_reschedule_booking y met_cancel_booking— piden agenda:write, que un cliente MCP no recibe por defecto: tiene que pedirlo a propósito.

Errores comunes

CódigoQué pasó
400 validation_errorUna fecha mal escrita, to antes que from, más de 31 días de huecos o una modalidad que el servicio no tiene. Al reprogramar: un horario que ya pasó, fuera del horario de atención, o una cita que no está confirmada.
403 missing_scopeLa key no trae agenda:read o agenda:write. Las keys emitidas antes de que existiera el scope no lo traen.
403 test_mode_restrictedUna key met_test_ intentó escribir en la agenda.
403 workspace_mismatchEl workspaceId de la ruta no es el de la key.
404 event_type_not_foundEl servicio no existe, es de otro workspace o está inactivo.
404 contact_not_foundEl contacto no es de este workspace.
404 booking_not_foundLa cita no existe en este workspace. Al reprogramar llega como not_found.
404 resource_not_foundAl reprogramar, el recurso no atiende ese servicio en este workspace.
409 slot_unavailableAl agendar, el horario ya no está libre (o ya pasó, o no coincide con un hueco). Vuelve a pedir los huecos.
409 booking_existsEl contacto ya tiene esa cita; el error trae su booking_id.
409 booking_not_confirmedSe intentó cancelar una cita que no está confirmada.
409 conflictAl reprogramar, el horario nuevo choca con otra cita.