Calendar
Reference for the six Calendar routes in the ChatISP API: look up appointment types, list and look up the team's calendar, book visits, callbacks and meetings from another system, and record completion or cancellation.
Overview
The ChatISP Calendar stores the team's appointments: technical visit, contact callback, installation, meeting. Each appointment can have an assignee, a team, a customer and a linked conversation, and automatically fires alerts to the assignee and reminders to the customer over WhatsApp. Through the API, your system books and tracks these appointments with the same rules as the screen.
Typical integration cases:
- ERP and work orders: the installation work order scheduled in the ERP becomes an appointment in the technician's calendar, with an automatic reminder to the customer the day before.
- Subscriber portal: the customer picks the visit time in the portal and the appointment is created with the confirmation already sent over WhatsApp.
- Field app: the technician sees the day's calendar and marks the visit as completed, with the outcome.
- Route planning: an external system reads the week's calendar by team to plan routes.
Routes
| Route | Method | Key permission | What it does |
|---|---|---|---|
agenda/tipos | GET | Look up calendar | Appointment types and the default duration of each one. |
agenda/listar | GET | Look up calendar | Appointments in a period, with filters. |
agenda/consultar | GET | Look up calendar | One appointment. |
agenda/criar | POST | Manage calendar | Books an appointment. |
agenda/concluir | POST | Manage calendar | Marks it as completed, with an outcome. |
agenda/cancelar | POST | Manage calendar | Cancels it, with a reason. |
Permissions are checked on the key, in Integrations, ChatISP API card. Manage calendar does not include reading: to book and then look up, check both. Authentication, call format, pagination and common errors are in the Introduction.
The key sees the whole company's calendar, for all teams and assignees. The panel's rules about who sees what apply to people, not to the API. Appointments booked through the API have origem set to api and, in the notes of the linked conversation, the action appears as done by API: <key name>.
The appointment object
Format used by every route that returns appointments. Dates in ISO 8601 with the server's time zone.
| Field | Type | Description |
|---|---|---|
id | integer | Appointment identifier. |
titulo | text | Title, up to 160 characters. |
tipo | object or null | {"id", "nome"} of the type; null without a type. |
descricao | text | Notes; empty when there are none. |
inicio / fim | date | Start and end. Always on whole minutes (seconds are zeroed). |
dia_inteiro | boolean | true for an all-day appointment: inicio at 00:00 and fim at 23:59 on the same day. |
local | text | Address or meeting point. |
link | text | Meeting link (for example, the Google Meet room created for the appointment). |
estado | text | agendado, concluido or cancelado. |
origem | text | manual (booked in the Calendar), chat (booked from a conversation), copiloto, ia or api. |
responsavel_id / responsavel | integer or null / text | Assignee. |
departamento_id | integer or null | Appointment team. |
conversa_id | integer or null | Linked conversation. |
cliente | object | {"telefone", "nome"}. Phone number with country code, digits only; empty when the appointment has no customer. |
desfecho | text | What was recorded when completing or canceling. |
criado_em / atualizado_em | date | Creation and last change of the record. |
{
"id": 342,
"titulo": "Technical visit: router replacement",
"tipo": {"id": 3, "nome": "Technical visit"},
"descricao": "Bring a dual band router. Blue gate, intercom 2.",
"inicio": "2026-10-02T09:00:00-03:00",
"fim": "2026-10-02T10:00:00-03:00",
"dia_inteiro": false,
"local": "Rua das Palmeiras, 120, Centro",
"link": "",
"estado": "agendado",
"origem": "api",
"responsavel_id": 7,
"responsavel": "Carlos Lima",
"departamento_id": 3,
"conversa_id": 5310,
"cliente": {"telefone": "5544999990000", "nome": "Maria Souza"},
"desfecho": "",
"criado_em": "2026-10-01T14:20:03-03:00",
"atualizado_em": "2026-10-01T14:20:03-03:00"
}States
estado | Meaning |
|---|---|
agendado | Active appointment. Only this state fires alerts to the assignee and reminders to the customer. |
concluido | Done. Pending alerts are discarded. |
cancelado | Will not happen. Pending alerts are discarded and the event mirrored in Google Calendar is removed. |
Appointment types
/api_publica.php?r=agenda/tiposReturns the registered appointment types, in screen order, including disabled ones. No parameters. The type defines the default duration, the lead times of the assignee alerts and customer reminders, and whether the Google Calendar event is created with a Meet room.
Field of tipos[] | Description |
|---|---|
id | Identifier, used in tipo_id. |
nome | Type name. |
ativo | false when disabled. |
duracao_min | Default duration in minutes, used to calculate the end when the booking provides neither fim nor duracao_min. |
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ --data-urlencode "r=agenda/tipos"
<?php
$url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
'r' => 'agenda/tipos',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')],
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.get(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "agenda/tipos"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
timeout=30,
).json(){
"ok": true,
"tipos": [
{"id": 1, "nome": "Meeting", "ativo": true, "duracao_min": 60},
{"id": 2, "nome": "Contact callback", "ativo": true, "duracao_min": 15},
{"id": 3, "nome": "Technical visit", "ativo": true, "duracao_min": 60},
{"id": 4, "nome": "Training", "ativo": true, "duracao_min": 90},
{"id": 5, "nome": "Presentation", "ativo": true, "duracao_min": 60}
]
}The types and the lead times of each one are configured by the ISP in the Calendar: see Appointment types.
List appointments
/api_publica.php?r=agenda/listarAppointments that overlap the requested period (start before the end of the period and end after its start), in order of start. An appointment from 23:00 to 01:00 appears both on the day it starts and on the next one.
| Parameter | Type | Description |
|---|---|---|
de | date | Start of the period. Default: today, 00:00. |
ate | date | End of the period. Default: 30 days after de, at 23:59:59. A date without a time counts until 23:59:59 of that day. The maximum period is 366 days. |
estado | text | agendado, concluido or cancelado. Without it, all of them. |
responsavel_id | integer | Only this person's. |
departamento_id | integer | Only this team's. |
tipo_id | integer | Only this type. |
conversa_id | integer | Only those linked to this conversation. |
telefone | text | Only this customer's (searched with and without the ninth digit). |
pagina / limite | integer | Numbered pagination: limite from 1 to 100 (default 50). |
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ --data-urlencode "r=agenda/listar" \ --data-urlencode "de=2026-10-02" \ --data-urlencode "ate=2026-10-02" \ --data-urlencode "responsavel_id=7" \ --data-urlencode "estado=agendado"
<?php
$url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
'r' => 'agenda/listar',
'de' => '2026-10-02',
'ate' => '2026-10-02',
'responsavel_id' => 7,
'estado' => 'agendado',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')],
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.get(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "agenda/listar", "de": "2026-10-02", "ate": "2026-10-02", "responsavel_id": 7, "estado": "agendado"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
timeout=30,
).json(){
"ok": true,
"periodo": {"de": "2026-10-02T00:00:00-03:00", "ate": "2026-10-02T23:59:59-03:00"},
"compromissos": [
{
"id": 342,
"titulo": "Technical visit: router replacement",
"tipo": {"id": 3, "nome": "Technical visit"},
"inicio": "2026-10-02T09:00:00-03:00",
"fim": "2026-10-02T10:00:00-03:00",
"estado": "agendado",
"local": "Rua das Palmeiras, 120, Centro",
"cliente": {"telefone": "5544999990000", "nome": "Maria Souza"},
"...": "other fields of the appointment object"
}
],
"paginacao": {"pagina": 1, "limite": 50, "total": 1, "paginas": 1}
}The periodo block returns the period actually used, which helps check the defaults when de or ate were omitted.
Look up an appointment
/api_publica.php?r=agenda/consultar| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Appointment id. |
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ --data-urlencode "r=agenda/consultar" \ --data-urlencode "id=342"
<?php
$url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
'r' => 'agenda/consultar',
'id' => 342,
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')],
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.get(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "agenda/consultar", "id": 342},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
timeout=30,
).json(){
"ok": true,
"compromisso": {"id": 342, "titulo": "Technical visit: router replacement", "...": "full appointment object"}
}Book an appointment
/api_publica.php?r=agenda/criarBooks an appointment in the agendado state. Title and start are required; the end can be given explicitly, by duration or by the type.
| Parameter | Type | Required | Description |
|---|---|---|---|
titulo | text | yes | From 1 to 160 characters. |
inicio | date | yes | Start date and time (ISO 8601 or YYYY-MM-DD HH:MM:SS in the server's time zone). |
fim | date | no | End date and time. Must be after the start. |
duracao_min | integer | no | Duration in minutes, used when fim is not sent. |
dia_inteiro | boolean | no | All-day appointment: takes up the inicio day from 00:00 to 23:59, and fim and duracao_min are ignored. |
tipo_id | integer | no | Appointment type (agenda/tipos). A type that does not exist or is disabled returns tipo_invalido. |
descricao | text | no | Notes, up to 5,000 characters. |
local | text | no | Address or meeting point, up to 255 characters. |
link | text | no | Meeting link, starting with http:// or https://, up to 500 characters. |
responsavel_id | integer | no | Assignee (active). |
departamento_id | integer | no | Appointment team. |
conversa_id | integer | no | Linked conversation. Fills in the customer's phone and name when they are not provided. |
telefone | text | no | Customer's WhatsApp, for confirmation and reminders. Same phone rule as the rest of the API. |
nome_cliente | text | no | Customer's name, up to 190 characters. Goes into the {nome} variable of the texts to the customer. |
confirmar_cliente | boolean | no | Sends the confirmation to the customer over WhatsApp right away. Without the parameter, the type's default applies (as in the screen form); without a type, nothing is sent. Send false to skip it even on a type that asks for confirmation. |
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=agenda/criar" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"titulo": "Technical visit: router replacement", "tipo_id": 3, "inicio": "2026-10-02T09:00:00-03:00", "local": "Rua das Palmeiras, 120, Centro", "responsavel_id": 7, "departamento_id": 3, "telefone": "44999990000", "nome_cliente": "Maria Souza", "confirmar_cliente": true}'<?php
$dados = [
'titulo' => 'Technical visit: router replacement',
'tipo_id' => 3,
'inicio' => '2026-10-02T09:00:00-03:00',
'local' => 'Rua das Palmeiras, 120, Centro',
'responsavel_id' => 7,
'departamento_id' => 3,
'telefone' => '44999990000',
'nome_cliente' => 'Maria Souza',
'confirmar_cliente' => true,
];
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=agenda/criar');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CHATISP_CHAVE'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($dados, JSON_UNESCAPED_UNICODE),
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.post(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "agenda/criar"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
json={"titulo": "Technical visit: router replacement", "tipo_id": 3, "inicio": "2026-10-02T09:00:00-03:00", "local": "Rua das Palmeiras, 120, Centro", "responsavel_id": 7, "departamento_id": 3, "telefone": "44999990000", "nome_cliente": "Maria Souza", "confirmar_cliente": True},
timeout=30,
).json(){
"ok": true,
"compromisso": {
"id": 342,
"titulo": "Technical visit: router replacement",
"inicio": "2026-10-02T09:00:00-03:00",
"fim": "2026-10-02T10:00:00-03:00",
"estado": "agendado",
"origem": "api",
"...": "other fields of the appointment object"
}
}How the end is calculated
- With
dia_inteiro: from the beginning to the end of theinicioday. - With
fim: the value given is used. An end equal to or before the start returnsperiodo_invalido. - With
duracao_min: start plus the duration. - With none of the three: start plus the type's default duration (at least 15 minutes); without a type, 60 minutes.
An appointment can last at most 30 days.
What happens when booking
- Scheduled alerts: the lead times of the assignee alert and the customer reminder come from the type. Without a type, the assignee is alerted 15 minutes before and the customer gets no reminder. Only alerts whose time is still in the future are scheduled. Details in Alerts and reminders.
- Assignee alert: the assignee receives "Appointment booked for you" over WhatsApp, with date, time, customer, location and link. Without an assignee, the alert goes to the department's team. It applies to people who have a verified WhatsApp and alerts turned on in My account.
- Customer confirmation: with
confirmar_cliente(or, without the parameter, when the type asks for confirmation), the customer immediately receives the confirmation text configured in Settings (with the variables{nome},{data},{hora},{detalhes}and the others). The message goes out through the linked conversation, through the most recent conversation with the number or, with no conversation at all, as a standalone message through the connected number. If the confirmation text in Settings is empty, nothing is sent. See Texts to the customer. - Google Calendar: if the assignee connected their Google account in My account, the appointment is mirrored in their Google calendar; if the type asks for a Google Meet room and
linkcame empty, the room is created and its address goes into thelinkfield before the confirmation and the alert go out. A Google failure never prevents the booking: the system tries again in the background. - Linked conversation: with
conversa_id, the conversation timeline records the appointment and, if the conversation is open, gets an internal note with title, date and time. The customer does not see the note.
As on the screen, where the Send confirmation to the customer now switch is already on for types that ask for confirmation, the API follows the type when confirmar_cliente is not sent. The reminders scheduled by the type go out normally, with or without confirmation.
The Calendar has no webhook events. To keep an external system up to date, query agenda/listar periodically for the period you care about.
Complete
/api_publica.php?r=agenda/concluirMarks the appointment as done and records the outcome. It only applies to an agendado appointment; in any other state, the response is 409 estado_invalido with the current estado.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Appointment id. |
desfecho | text | no | What happened, up to 2,000 characters. |
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=agenda/concluir" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"id": 342, "desfecho": "Router replaced, Wi-Fi tested with the customer."}'<?php
$dados = [
'id' => 342,
'desfecho' => 'Router replaced, Wi-Fi tested with the customer.',
];
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=agenda/concluir');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CHATISP_CHAVE'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($dados, JSON_UNESCAPED_UNICODE),
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.post(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "agenda/concluir"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
json={"id": 342, "desfecho": "Router replaced, Wi-Fi tested with the customer."},
timeout=30,
).json(){
"ok": true,
"compromisso": {
"id": 342,
"estado": "concluido",
"desfecho": "Router replaced, Wi-Fi tested with the customer.",
"...": "other fields of the appointment object"
}
}Alerts not yet sent are discarded. The linked conversation records the completion in its timeline and, if it is open, gets an internal note. The Google Calendar mirror, when it exists, is synced.
Cancel
/api_publica.php?r=agenda/cancelarCancels the appointment and records the reason. It only applies to an agendado appointment; in any other state, the response is 409 estado_invalido with the current estado.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Appointment id. |
desfecho | text | no | Cancellation reason, up to 2,000 characters. |
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=agenda/cancelar" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"id": 342, "desfecho": "Customer asked to reschedule for next week."}'<?php
$dados = [
'id' => 342,
'desfecho' => 'Customer asked to reschedule for next week.',
];
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=agenda/cancelar');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CHATISP_CHAVE'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($dados, JSON_UNESCAPED_UNICODE),
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.post(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "agenda/cancelar"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
json={"id": 342, "desfecho": "Customer asked to reschedule for next week."},
timeout=30,
).json(){
"ok": true,
"compromisso": {
"id": 342,
"estado": "cancelado",
"desfecho": "Customer asked to reschedule for next week.",
"...": "other fields of the appointment object"
}
}Pending alerts are discarded, the event mirrored in Google Calendar is removed (and the Meet room with it) and the linked conversation records the cancellation. The customer does not receive an automatic cancellation message: if you want to let them know, send it through the mensagens/enviar route.
The API does not reschedule or reopen appointments. To reschedule, cancel the current one and book a new one at the right time; if confirmation is on, the customer receives the new date.
Calendar route errors
In addition to the errors common to the whole API, described in Errors:
error | HTTP | Routes | Cause |
|---|---|---|---|
permissao | 403 | all | The key does not have Look up calendar (read) or Manage calendar (write). The escopo field says which one is missing. |
parametro_obrigatorio | 400 | consultar, criar, concluir, cancelar | id or inicio is missing (see campo). |
nao_encontrado | 404 | consultar, concluir, cancelar | No appointment with that id. |
tipo_invalido | 400 | criar | Type does not exist or is disabled. |
estado_invalido | 409 | concluir, cancelar | The appointment is not agendado (it was already completed or canceled). The estado field carries the current state. |
titulo_invalido | 400 | criar | Empty title or longer than 160 characters. |
data_invalida | 400 | listar, criar | Date that could not be parsed (see campo). Use ISO 8601 or YYYY-MM-DD HH:MM:SS; words such as "tomorrow" are not accepted. |
periodo_invalido | 400 | listar, criar | In the list: ate before de, or a period over 366 days (with maximo_dias). In the booking: end equal to or before the start. |
link_invalido | 400 | criar | Link that does not start with http:// or https://, has a space or exceeds 500 characters. |
telefone_invalido | 400 | listar, criar | The phone number is not a valid WhatsApp number. |
parametro_invalido | 400 | listar, criar | Unknown estado (list in aceitos), assignee or department that does not exist, inactive assignee, description over 5,000 characters, duration over 30 days, number with letters, limite over 100. |
{"ok": false, "error": "periodo_invalido", "maximo_dias": 366}