A visitor asks for a call. The agent reads the free times of your agenda, offers three, books the one the person picks and confirms the day and hour. If plans change, it moves or cancels the same meeting. A reminder goes out by WhatsApp the day before, and your systems hear about every change by webhook.
Who it is for. Firms and clinics that sell an intro call or a consultation. Scopes.agents:write, pipelines:write, scheduling:read, scheduling:write, whatsapp:write, webhooks:write, cases:read, cases:write, cases:converse, contacts:write.
The agent books through three tools that Lelemon gives it: check_availability, schedule_meeting and cancel_meeting. They read and write the default agenda of the organization, so the agent never holds a scheduling key and you do not need a backend for it. You grant them like any other tool.
Code
Visitor Agent Lelemon Your endpoint | "¿cuándo?" -> check_availability -------------> free slots | <- 3 times | "el segundo" -> schedule_meeting -------------> meeting.scheduled --------> WhatsApp reminder, sendAt = start - 24 h | case moves to "Meeting booked" | "muévela" -> schedule_meeting (new) + cancel_meeting (old) | meeting.scheduled + meeting.canceled -> new reminder, old one dropped | "cancela" -> cancel_meeting -------------------> meeting.canceled ---------> reminder dropped
An empty list means the organization has not set up scheduling. Routes take the agd_ id, never the slug. See Schedule meetings.
1. Write the agent
The prompt fixes the voice, the order of questions, how times are offered (never invented, at most three, always in Santiago time), how to move a meeting and what to do when a booking fails. To move a meeting the agent books the new time first and cancels the old one after: the meeting id that schedule_meeting returned stays in its conversation, so it can cancel it later.
2. Create the pipeline and the field, then activate
When the agent books, the case moves by itself to the stage whose name says meeting (Meeting booked). That stage keeps the same agent, so the person can still ask to move or cancel the meeting. service_interest has source: "agent": the agent writes it with save_case_data.
Code
pipeline {"id":"ppl_2E381t7Oh6cOeMwLPs8Mw6","stages":["Scheduling","Meeting booked","Needs a person"]} readiness {"ready":true,"missing":[]}
3. Connect a WhatsApp number
The reminder goes out through a number of the organization, and a template can only be created when one is active. The connection returns a setupUrl; the owner of the number opens it to finish. The script reads the id of a number you already connected.
4. Create the reminder template
Reminders go out of the 24-hour window, so they are templates. A utility template with named variables is reviewed by WhatsApp: wait for approved before you use it. The script reads the id of an approved template from LELEMON_REMINDER_TEMPLATE_ID.
Granting is one call per tool. The scheduling tools take no configuration: they use the default agenda of the organization. save_case_data lets the agent write service_interest and transfer_to_human sends the case to Needs a person.
6. Listen to meetings
meeting.* events fire for every meeting of the organization, whoever created it: the agent, your API call or the dashboard. Each one carries the meeting. See Receive events.
7. Keep the reminder in step with the meeting
The agent books and cancels, so the reminder is yours to keep. When a meeting is scheduled, find the contact by the email the agent collected and schedule the template for 24 hours before the start with sendAt. When a meeting is canceled, cancel the reminder. A meeting moved from the dashboard or by POST /v2/scheduling/meetings/{id}/reschedule arrives as meeting.rescheduled: replace the reminder.
In production keep the map of reminders in your database, keyed by the meeting id.
8. Try it: book, move, cancel
Open a case for a contact and write as the contact. The agent answers in the background and you hear about it with case.turn_completed.
The agent calls check_availability and offers three times in Santiago time:
Code
contact Hola, soy Camila Soto de la Panadería Doña Rosa. Quiero ver con ustedes la contabilidad mensual. ¿Cuándo me pueden llamar esta semana? agent Hola Camila, soy Sol, de Rivas & Co. Tengo estos horarios para una llamada de 30 minutos el lunes 2 de noviembre:1. Lunes 2 de noviembre a las 09:302. Lunes 2 de noviembre a las 11:303. Lunes 2 de noviembre a las 15:00¿Cuál te acomoda?
She picks one and gives her email. The agent calls schedule_meeting, confirms the day and the hour, and your endpoint receives meeting.scheduled. The case moves to Meeting booked and your handler schedules the reminder:
Code
await say('El segundo horario me sirve. Mi correo es [email protected]');
Code
contact El segundo horario me sirve. Mi correo es [email protected] agent ¡Listo, Camila! Quedó agendada tu llamada de 30 minutos para el lunes 2 de noviembre a las 11:30.Te va a llegar la invitación con el enlace de videollamada al correo [email protected]. Además, te enviaremos un recordatorio por WhatsApp el día antes.¿Hay algo más en lo que te pueda ayudar? webhook {"type":"meeting.scheduled","meeting":{"id":"mtg_3dMEFbGkZfg3ACM722iDoY","end":"2026-11-02T15:00:00.000Z","start":"2026-11-02T14:30:00.000Z","object":"meeting","status":"scheduled","context":"Interés: contabilidad mensual. Llamada de presentación de 30 minutos.","agendaId":"agd_5JKS4QUqBmhZdwhD7SdGGa","meetLink":"https://meet.example.com/a2edda12-387","timezone":"America/Santiago","createdAt":"2026-11-01T04:46:24.802Z","updatedAt":"2026-11-01T04:46:24.802Z","canceledAt":null,"companyName":"Panadería Doña Rosa","attendeeName":"Camila Soto","cancelReason":null,"attendeeEmail":"[email protected]","attendeePhone":null,"durationMinutes":30}} case {"stage":"Meeting booked","fields":{"service_interest":"monthly_accounting"},"state":"waiting_for_contact"} reminders ["wam_29ktVd28TqXgmz3lcf3RlJ"]
To move it, she only has to ask. The agent books the new time and cancels the old meeting, so you receive meeting.canceled for the old one and meeting.scheduled for the new one, and your handler replaces the reminder. To cancel, she asks and the agent calls cancel_meeting:
Code
await say('Se me cruzó algo. ¿La puedes mover al primer horario libre de esa misma tarde?');await say('Mejor déjala cancelada, no voy a poder esta semana.');
Code
contact Se me cruzó algo. ¿La puedes mover al primer horario libre de esa misma tarde? agent Listo, Camila. Tu llamada quedó movida para el lunes 2 de noviembre a las 12:30, y la de las 11:30 quedó cancelada.Te va a llegar la invitación con el enlace de videollamada al correo [email protected]. También te enviaremos un recordatorio por WhatsApp el día antes. webhook {"type":"meeting.canceled","meeting":{"id":"mtg_3dMEFbGkZfg3ACM722iDoY","end":"2026-11-02T15:00:00.000Z","start":"2026-11-02T14:30:00.000Z","object":"meeting","status":"canceled","context":"Interés: contabilidad mensual. Llamada de presentación de 30 minutos.","agendaId":"agd_5JKS4QUqBmhZdwhD7SdGGa","meetLink":"https://meet.example.com/a2edda12-387","timezone":"America/Santiago","createdAt":"2026-11-01T04:46:24.802Z","updatedAt":"2026-11-01T04:46:40.353Z","canceledAt":"2026-11-01T04:46:40.353Z","companyName":"Panadería Doña Rosa","attendeeName":"Camila Soto","cancelReason":"Reagendada por la persona para el primer horario libre de la tarde del mismo día","attendeeEmail":"[email protected]","attendeePhone":null,"durationMinutes":30}} webhook {"type":"meeting.scheduled","meeting":{"id":"mtg_4chqVbXPed3jfFocylQW8F","end":"2026-11-02T16:00:00.000Z","start":"2026-11-02T15:30:00.000Z","object":"meeting","status":"scheduled","context":"Interés: contabilidad mensual. Llamada de presentación de 30 minutos. Reagendada desde las 11:30 a. m. del mismo día.","agendaId":"agd_5JKS4QUqBmhZdwhD7SdGGa","meetLink":"https://meet.example.com/a0bae6b5-165","timezone":"America/Santiago","createdAt":"2026-11-01T04:46:37.812Z","updatedAt":"2026-11-01T04:46:37.812Z","canceledAt":null,"companyName":"Panadería Doña Rosa","attendeeName":"Camila Soto","cancelReason":null,"attendeeEmail":"[email protected]","attendeePhone":null,"durationMinutes":30}}
Code
contact Mejor déjala cancelada, no voy a poder esta semana. agent Listo, Camila, cancelé la llamada del lunes 2 de noviembre a las 12:30. Si más adelante quieres retomar la conversación sobre la contabilidad mensual, me escribes y te busco otro horario. webhook {"type":"meeting.canceled","meeting":{"id":"mtg_4chqVbXPed3jfFocylQW8F","end":"2026-11-02T16:00:00.000Z","start":"2026-11-02T15:30:00.000Z","object":"meeting","status":"canceled","context":"Interés: contabilidad mensual. Llamada de presentación de 30 minutos. Reagendada desde las 11:30 a. m. del mismo día.","agendaId":"agd_5JKS4QUqBmhZdwhD7SdGGa","meetLink":"https://meet.example.com/a0bae6b5-165","timezone":"America/Santiago","createdAt":"2026-11-01T04:46:37.812Z","updatedAt":"2026-11-01T04:46:47.809Z","canceledAt":"2026-11-01T04:46:47.809Z","companyName":"Panadería Doña Rosa","attendeeName":"Camila Soto","cancelReason":"La persona no podrá asistir esta semana","attendeeEmail":"[email protected]","attendeePhone":null,"durationMinutes":30}} reminders left []
The tool offers the nearest ten free times, so a request for a day beyond them is answered with those ten. A slot taken between the read and the booking makes schedule_meeting fail; the agent reads the availability again and offers another time.
9. Cancel from your own system
Your own system can cancel too, for example from an ops screen. The attendee is told, the slot is freed, the meeting stays readable with status: "canceled" and you receive meeting.canceled. A meeting that is already canceled answers 409 meeting_not_active.
Notes
Booking the same start twice answers 409.
The reminder is a message with sendAt: it is stored as scheduled and sent at that time.
Booking needs an active member in the organization to host the meeting; every organization has its owner.
What you see in the dashboard
Inbox: the case with the thread and the field service_interest in the card.
Pipeline: the case in Scheduling until the agent books, then Meeting booked.
Mi Negocio, Agendamiento: the meeting with its attendee, time and status, moved or canceled.
Agent, Activity: each tool call with its input and output.
curl https://api.lelemon.cl/v2/whatsapp/templates \ -H "Authorization: Bearer $LELEMON_API_KEY" \ -H "Idempotency-Key: tpl-reminder-1" \ --json '{ "name": "meeting_reminder", "language": "es", "category": "utility", "body": "Hola {{name}}, te recordamos tu llamada con Rivas & Co. el {{when}}. Si no puedes, responde a este mensaje y la movemos.", "examples": { "name": "Camila", "when": "jueves 5 de noviembre a las 10:00" }, "phoneNumberIds": "all" }'
curl -X PUT https://api.lelemon.cl/v2/agents/agt_.../tools/check_availability \ -H "Authorization: Bearer $LELEMON_API_KEY"curl -X PUT https://api.lelemon.cl/v2/agents/agt_.../tools/schedule_meeting \ -H "Authorization: Bearer $LELEMON_API_KEY"curl -X PUT https://api.lelemon.cl/v2/agents/agt_.../tools/cancel_meeting \ -H "Authorization: Bearer $LELEMON_API_KEY"