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

RouteMethodKey permissionWhat it does
agenda/tiposGETLook up calendarAppointment types and the default duration of each one.
agenda/listarGETLook up calendarAppointments in a period, with filters.
agenda/consultarGETLook up calendarOne appointment.
agenda/criarPOSTManage calendarBooks an appointment.
agenda/concluirPOSTManage calendarMarks it as completed, with an outcome.
agenda/cancelarPOSTManage calendarCancels 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.

FieldTypeDescription
idintegerAppointment identifier.
titulotextTitle, up to 160 characters.
tipoobject or null{"id", "nome"} of the type; null without a type.
descricaotextNotes; empty when there are none.
inicio / fimdateStart and end. Always on whole minutes (seconds are zeroed).
dia_inteirobooleantrue for an all-day appointment: inicio at 00:00 and fim at 23:59 on the same day.
localtextAddress or meeting point.
linktextMeeting link (for example, the Google Meet room created for the appointment).
estadotextagendado, concluido or cancelado.
origemtextmanual (booked in the Calendar), chat (booked from a conversation), copiloto, ia or api.
responsavel_id / responsavelinteger or null / textAssignee.
departamento_idinteger or nullAppointment team.
conversa_idinteger or nullLinked conversation.
clienteobject{"telefone", "nome"}. Phone number with country code, digits only; empty when the appointment has no customer.
desfechotextWhat was recorded when completing or canceling.
criado_em / atualizado_emdateCreation and last change of the record.
JSON (appointment object)
{
  "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

estadoMeaning
agendadoActive appointment. Only this state fires alerts to the assignee and reminders to the customer.
concluidoDone. Pending alerts are discarded.
canceladoWill not happen. Pending alerts are discarded and the event mirrored in Google Calendar is removed.

Appointment types

GET/api_publica.php?r=agenda/tipos

Returns 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
idIdentifier, used in tipo_id.
nomeType name.
ativofalse when disabled.
duracao_minDefault duration in minutes, used to calculate the end when the booking provides neither fim nor duracao_min.
curl
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \
  -H "Authorization: Bearer $CHATISP_CHAVE" \
  --data-urlencode "r=agenda/tipos"
PHP
<?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);
Python
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()
Response
{
  "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

GET/api_publica.php?r=agenda/listar

Appointments 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.

ParameterTypeDescription
dedateStart of the period. Default: today, 00:00.
atedateEnd 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.
estadotextagendado, concluido or cancelado. Without it, all of them.
responsavel_idintegerOnly this person's.
departamento_idintegerOnly this team's.
tipo_idintegerOnly this type.
conversa_idintegerOnly those linked to this conversation.
telefonetextOnly this customer's (searched with and without the ninth digit).
pagina / limiteintegerNumbered pagination: limite from 1 to 100 (default 50).
curl
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
<?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);
Python
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()
Response
{
  "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

GET/api_publica.php?r=agenda/consultar
ParameterTypeRequiredDescription
idintegeryesAppointment id.
curl
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
<?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);
Python
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()
Response
{
  "ok": true,
  "compromisso": {"id": 342, "titulo": "Technical visit: router replacement", "...": "full appointment object"}
}

Book an appointment

POST/api_publica.php?r=agenda/criar

Books an appointment in the agendado state. Title and start are required; the end can be given explicitly, by duration or by the type.

ParameterTypeRequiredDescription
titulotextyesFrom 1 to 160 characters.
iniciodateyesStart date and time (ISO 8601 or YYYY-MM-DD HH:MM:SS in the server's time zone).
fimdatenoEnd date and time. Must be after the start.
duracao_minintegernoDuration in minutes, used when fim is not sent.
dia_inteirobooleannoAll-day appointment: takes up the inicio day from 00:00 to 23:59, and fim and duracao_min are ignored.
tipo_idintegernoAppointment type (agenda/tipos). A type that does not exist or is disabled returns tipo_invalido.
descricaotextnoNotes, up to 5,000 characters.
localtextnoAddress or meeting point, up to 255 characters.
linktextnoMeeting link, starting with http:// or https://, up to 500 characters.
responsavel_idintegernoAssignee (active).
departamento_idintegernoAppointment team.
conversa_idintegernoLinked conversation. Fills in the customer's phone and name when they are not provided.
telefonetextnoCustomer's WhatsApp, for confirmation and reminders. Same phone rule as the rest of the API.
nome_clientetextnoCustomer's name, up to 190 characters. Goes into the {nome} variable of the texts to the customer.
confirmar_clientebooleannoSends 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
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
<?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);
Python
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()
Response
{
  "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

  1. With dia_inteiro: from the beginning to the end of the inicio day.
  2. With fim: the value given is used. An end equal to or before the start returns periodo_invalido.
  3. With duracao_min: start plus the duration.
  4. 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 link came empty, the room is created and its address goes into the link field 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

POST/api_publica.php?r=agenda/concluir

Marks 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.

ParameterTypeRequiredDescription
idintegeryesAppointment id.
desfechotextnoWhat happened, up to 2,000 characters.
curl
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
<?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);
Python
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()
Response
{
  "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

POST/api_publica.php?r=agenda/cancelar

Cancels 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.

ParameterTypeRequiredDescription
idintegeryesAppointment id.
desfechotextnoCancellation reason, up to 2,000 characters.
curl
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
<?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);
Python
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()
Response
{
  "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:

errorHTTPRoutesCause
permissao403allThe key does not have Look up calendar (read) or Manage calendar (write). The escopo field says which one is missing.
parametro_obrigatorio400consultar, criar, concluir, cancelarid or inicio is missing (see campo).
nao_encontrado404consultar, concluir, cancelarNo appointment with that id.
tipo_invalido400criarType does not exist or is disabled.
estado_invalido409concluir, cancelarThe appointment is not agendado (it was already completed or canceled). The estado field carries the current state.
titulo_invalido400criarEmpty title or longer than 160 characters.
data_invalida400listar, criarDate 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_invalido400listar, criarIn 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_invalido400criarLink that does not start with http:// or https://, has a space or exceeds 500 characters.
telefone_invalido400listar, criarThe phone number is not a valid WhatsApp number.
parametro_invalido400listar, criarUnknown 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.
Error example
{"ok": false, "error": "periodo_invalido", "maximo_dias": 366}