Use a base https://botozap.com.br/api/v1 com Authorization: Bearer $BOTOZAP_KEY ou X-API-Key. A Conta é derivada da chave; IDs de contato, serviço, pessoa, conversa e registros de CRM são validados no servidor. Os exemplos mostram o caminho depois desse prefixo.
A Agenda usa appointments:read e appointments:write; o Google Calendar usa calendar:read e calendar:write, escopos independentes. Todas estas rotas exigem chave live: chave sandbox recebe 403 sandbox_forbidden. Para vincular compromissos a oportunidades e demandas, consulte CRM e Radar.
Rotas
| Método | Caminho | Resultado |
|---|---|---|
| GET / POST | /appointments | Listar ou criar compromissos. |
| GET / PATCH / DELETE | /appointments/{id} | Consultar, editar ou excluir. |
| GET | /appointments/{id}/history | Histórico do compromisso. |
| GET | /appointments/availability | Horários livres de uma pessoa para um serviço. |
| GET / POST | /appointments/services | Listar ou criar serviços. |
| PATCH | /appointments/services/{id} | Editar ou desativar um serviço. |
| GET / POST | /appointments/schedules | Listar ou criar jornadas. |
| PATCH | /appointments/schedules/{id} | Editar uma jornada. |
| GET / POST | /appointments/exceptions | Listar ou criar exceções. |
| PATCH / DELETE | /appointments/exceptions/{id} | Editar ou remover uma exceção. |
Rotas do Google Calendar estão em Google Calendar e Meet. IDs malformados no caminho ou em filtros retornam 422 invalid_id.
Compromissos
| Campo | Tipo | Regras |
|---|---|---|
contact_id | UUID | Obrigatório na criação e imutável. O Cliente do compromisso vem do contato; customer_id no corpo é ignorado. |
scheduled_at | ISO 8601 | Início, com fuso horário. Obrigatório na criação. |
ends_at | ISO 8601 | Término. Padrão: início mais a duração do serviço, ou 60 minutos sem serviço. Remarcar só o início preserva a duração. |
title | texto | 1 a 200 caracteres. Padrão Agendamento. |
time_zone | texto | Fuso IANA, como America/Sao_Paulo (padrão). |
status | enum | pending, confirmed, cancelled, completed, no_show. Padrão confirmed. |
service_id | UUID ou null | Serviço ativo do mesmo Cliente. Aplica duração, buffers, antecedência e horizonte. |
owner_user_id | UUID ou null | Membro da Conta responsável. Com responsável, a reserva segue a jornada e bloqueia sobreposições. |
cancellation_reason | texto ou null | Até 2000 caracteres. Obrigatório para cancelled. |
location | texto ou null | Até 1000 caracteres. |
note | texto ou null | Até 16000 caracteres. |
conversation_id | UUID ou null | Conversa do mesmo contato. |
opportunity_id, demand_id | UUID ou null | Registro de CRM do mesmo contato e Cliente. |
meeting_requested | booleano | Solicita link do Google Meet. Veja Meet. |
A resposta traz o compromisso em data, com id, customer_id, revision, buffer_before_minutes, buffer_after_minutes, meeting_url, o estado da sincronização Google (google_sync_error, google_conflict) e os timestamps. Campos desconhecidos no corpo são ignorados.
Campos obsoletos:google_etagegoogle_base_datasão dados internos da sincronização com o Google Calendar e deixam de vir na resposta em 26/10/2026. Não dependa deles; o SDK 0.6.0 já os marca como obsoletos.
Estados
| Estado | No painel | Efeito |
|---|---|---|
pending | A confirmar | Aguarda confirmação; aparece no Radar como compromisso a confirmar. |
confirmed | Confirmado | Padrão na criação. Registra o momento da confirmação. |
cancelled | Cancelado | Exige cancellation_reason. Libera o horário e interrompe as réguas do compromisso. |
completed | Compareceu | Comparecimento registrado. Só depois do início do compromisso e nunca a partir de cancelled (422 invalid_request). Encerra lembretes anteriores e mantém o pós-atendimento. |
no_show | Não compareceu | Ausência registrada. Só depois do início do compromisso e nunca a partir de cancelled (422 invalid_request). Libera o horário e interrompe as réguas do compromisso. |
Regras de reserva
Com owner_user_id e estado pending ou confirmed, criar, remarcar, trocar serviço ou responsável ou reabrir um compromisso passa por estas verificações:
- Outro compromisso ativo da mesma pessoa, somando os buffers, retorna
409 time_conflict. - A pessoa precisa ter jornada publicada no Cliente, e o horário precisa caber numa janela do dia local, sem atravessar a meia-noite. Fora disso,
422 invalid_request. - Exceção
availableno dia substitui as janelas semanais; exceçãobusysobreposta retorna409 time_conflict. - Com serviço, o início respeita a antecedência mínima e o horizonte de reserva; fora disso,
422 invalid_request. - Ocupação importada do Google sobreposta retorna
409 time_conflict; ocupação desatualizada bloqueia a reserva com422 invalid_requestaté sincronizar.
Sem responsável, as regras de jornada e ocupação não se aplicam. Para trocar o responsável de um compromisso já enviado ao Google, cancele-o, aguarde a sincronização e só então escolha a nova pessoa e o novo horário.
Criar com Idempotency-Key
POST /appointments
Idempotency-Key: pedido-8431-reserva
{
"contact_id": "UUID-DO-CONTATO",
"scheduled_at": "2026-10-05T14:00:00-03:00",
"service_id": "UUID-DO-SERVICO",
"owner_user_id": "UUID-DA-PESSOA",
"title": "Avaliação inicial"
}A resposta é 201. Envie Idempotency-Key de 8 a 200 caracteres para repetir a chamada com segurança: a mesma chave com o mesmo corpo devolve o compromisso original, sem nova reserva; a mesma chave com outro corpo, ou depois de excluído o compromisso original, retorna 409 conflict. Chave fora do tamanho retorna 422 invalid_request. Consultar a disponibilidade não reserva o horário.
Editar
PATCH /appointments/{id} recebe os campos que mudam e expected_revision com a revision lida. Versão obsoleta retorna 409 conflict. O PATCH legado, que altera apenas note e/ou scheduled_at, continua aceito sem revisão e grava sobre a versão atual. Qualquer outro campo sem revisão retorna 422 revision_required.
PATCH /appointments/{id}
{
"expected_revision": 2,
"status": "cancelled",
"cancellation_reason": "Contato pediu para remarcar"
}Consultar e excluir
GET /appointments/{id} retorna o compromisso. DELETE /appointments/{id} responde 204. Um compromisso vinculado a evento do Google só pode ser excluído depois de cancelado e com o cancelamento confirmado no Google; antes disso, 409 calendar_sync_pending. Alteração simultânea ou sincronização em andamento retornam 409 conflict.
Listar
| Parâmetro | Valores |
|---|---|
customer_id, contact_id, service_id | UUID. |
owner_user_id | UUID ou unassigned. |
status | Um dos estados acima; outro valor retorna 422 invalid_status. |
date | YYYY-MM-DD: compromissos que começam nesse dia em UTC. Tem precedência sobre from/to. |
from, to | ISO 8601 com fuso: compromissos que se sobrepõem ao intervalo. to precisa ser posterior a from. |
page, per_page | Padrão 20, máximo 100. |
A ordem é pelo início, do mais cedo para o mais tarde. Data inválida retorna 422 invalid_date; intervalo invertido, 422 invalid_range.
Histórico
GET /appointments/{id}/history?page=N retorna 20 registros por página, do mais recente para o mais antigo. Cada registro tem event_type (created, rescheduled, updated ou o novo estado), source (api, dashboard ou uma origem da sincronização Google), actor_user_id (null pela API), before_data, after_data e created_at.
Erros dos compromissos
| Status | Código | Quando |
|---|---|---|
| 400 | invalid_json | Corpo que não é JSON. |
| 404 | not_found | Compromisso inexistente ou de outra Conta. |
| 409 | conflict | Revisão obsoleta, Idempotency-Key reutilizada com outro corpo ou sincronização em andamento. |
| 409 | time_conflict | Horário ocupado por outro compromisso, bloqueio ou evento do Google. |
| 409 | calendar_sync_pending | Exclusão antes de o cancelamento chegar ao Google. |
| 422 | missing_scheduled_at, invalid_scheduled_at | Início ausente na criação ou fora do formato ISO com fuso. |
| 422 | invalid_contact | contact_id inválido ou de outra Conta. |
| 422 | revision_required | Edição de campo novo sem expected_revision. |
| 422 | no_updatable_fields | PATCH sem campo editável. |
| 422 | invalid_date, invalid_range, invalid_status | Filtros inválidos na listagem. |
| 422 | invalid_request | Campo, vínculo, responsável, jornada, antecedência ou motivo de cancelamento inválidos. A mensagem indica a regra. |
Disponibilidade
GET /appointments/availability exige customer_id, owner_user_id, service_id, from e to. Na remarcação, envie exclude_id com o compromisso atual para que ele não ocupe o próprio horário.
GET /appointments/availability?customer_id=UUID&owner_user_id=UUID&service_id=UUID&from=2026-10-05T00:00:00-03:00&to=2026-10-12T00:00:00-03:00
{
"data": {
"time_zone": "America/Sao_Paulo",
"slots": [
{ "starts_at": "2026-10-05T12:00:00.000Z", "ends_at": "2026-10-05T13:00:00.000Z" }
],
"schedule_published": true
}
}Os horários seguem a jornada e as exceções da pessoa, a grade, duração, buffers, antecedência e horizonte do serviço, os compromissos ativos e a ocupação importada do Google. time_zone é o fuso da jornada; schedule_published é false quando a jornada não tem janelas.
- Consulte até 31 dias por chamada; intervalo maior ou invertido retorna
422 invalid_range. - Pessoa sem jornada no Cliente retorna
422 schedule_missing. - Serviço inexistente, de outro Cliente ou inativo retorna
404 not_found, assim comoexclude_idde outro Cliente.
Serviços, jornadas e exceções
As listagens exigem customer_id e retornam páginas fixas de 100 itens em ordem de criação; per_page é ignorado. POST retorna 201. PATCH exige expected_revision (422 revision_required sem ela; 409 conflict quando obsoleta) e valida o registro resultante. O Cliente de uma configuração não muda depois de criada.
Serviços
| Campo | Regras |
|---|---|
customer_id | UUID do Cliente. |
name | 1 a 120 caracteres. |
description | Opcional, até 2000 caracteres. |
duration_minutes | 5 a 1440. |
slot_minutes | Grade dos horários oferecidos, 5 a 240. |
buffer_before_minutes, buffer_after_minutes | 0 a 240. |
minimum_notice_minutes | Antecedência mínima, 0 a 43200. |
booking_horizon_days | Horizonte de reserva, 1 a 366. |
active | Booleano. |
Na criação, todos os campos são obrigatórios, exceto description. Não há DELETE: desative com active: false. Compromissos existentes são preservados, mas o serviço inativo deixa de aceitar novas reservas e remarcações.
Jornadas da equipe
POST /appointments/schedules
{
"customer_id": "UUID-DO-CLIENTE",
"owner_user_id": "UUID-DA-PESSOA",
"time_zone": "America/Sao_Paulo",
"windows": [
{ "dow": 1, "start": "09:00", "end": "12:00" },
{ "dow": 1, "start": "13:00", "end": "18:00" },
{ "dow": 6, "start": "08:00", "end": "24:00" }
]
}Cada pessoa tem uma jornada por Cliente; para mudar, use PATCH. Criar outra jornada para a mesma pessoa, ou trocar o owner_user_id para alguém que já tem jornada, retorna 409 schedule_exists. dow vai de 0 (domingo) a 6 (sábado); start é HH:MM; end é HH:MM ou 24:00 e precisa ser posterior ao início. São até 50 janelas, no fuso IANA informado.
Exceções
| Campo | Regras |
|---|---|
customer_id, owner_user_id | Cliente e pessoa da exceção. |
local_date | Data local YYYY-MM-DD, no fuso da jornada. |
start_minute | 0 a 1439, minutos desde a meia-noite. |
end_minute | 1 a 1440, maior que start_minute. |
kind | available substitui as janelas daquele dia; busy bloqueia o intervalo. |
reason | Opcional, até 500 caracteres. |
Uma exceção com a mesma pessoa, local_date, intervalo e kind de outra já existente retorna 409 exception_exists, no POST e no PATCH.
DELETE /appointments/exceptions/{id} exige um corpo JSON e responde 204:
DELETE /appointments/exceptions/{id}
{ "expected_revision": 1 }Sem corpo JSON, retorna 400 invalid_json; sem expected_revision inteiro, 422 revision_required; revisão obsoleta ou exceção inexistente, 409 conflict.
Google Calendar e Meet
Cada pessoa conecta a própria conta Google pelo painel. A API não inicia OAuth nem aceita user_id para representar uma pessoa, e nunca retorna tokens: as credenciais ficam no Vault. A chave com calendar:* opera sobre todas as conexões da Conta.
Conectar e reconectar
- Em Calendários, o painel abre
/api/calendar/oauth/connect(para reconectar, com?connection_id=UUID). - A pessoa vê a página de uso de dados e confirma o consentimento da política de 24/09/2026. O envio é aceito só pela sessão logada, na mesma origem e na mesma Conta.
- O BotoZap registra o consentimento e redireciona ao Google com PKCE e acesso offline.
- O Google volta para
/api/calendar/oauth/callback; o BotoZap guarda as credenciais e carrega o catálogo de calendários.
É preciso conceder leitura e edição de eventos e leitura dos calendários e da ocupação. Uma autorização parcial é recusada. Quando o Google revoga o acesso, a conexão passa a reauth_required e deve ser reconectada.
Rotas
| Operação | Contrato |
|---|---|
GET /calendar/connections | Conexões da Conta, paginadas: id, owner_user_id, label, status (connected, reauth_required, disconnected ou error), last_error, last_synced_at e created_at. |
GET /calendar/connections/{id}/calendars | Catálogo completo da conexão, sem paginação. |
POST /calendar/connections/{id}/refresh | Relê o catálogo no Google e devolve a mesma lista. |
PATCH /calendar/selections/{id} | Define destino e ocupação de um calendário do catálogo. |
GET /calendar/jobs | Fila de sincronização; filtro opcional connection_id. |
POST /calendar/jobs/{id}/retry | Solicita nova tentativa. |
POST /calendar/conflicts/{id}/resolve | Resolve o conflito de um compromisso. |
DELETE /calendar/connections/{id} | Desconecta; 204. |
Calendários e seleção
Cada item do catálogo traz id, connection_id, external_calendar_id, summary, time_zone, access_role, destination, include_busy, conference_supported, synced_at e revision. Use esse id em /calendar/selections/{id}; o ID do Google fica em external_calendar_id.
PATCH /calendar/selections/{id}
{ "destination": true, "include_busy": true, "expected_revision": 1 }
200
{ "data": { "updated": true } }- Os três campos são obrigatórios; faltando algum,
422 invalid_request. destinationrecebe os compromissos novos da pessoa e exige calendário com permissão de escrita (ownerouwriter); cada pessoa tem um destino, e marcar um desmarca o anterior.include_busyimporta a ocupação do calendário para bloquear horários. Desmarcar remove a ocupação importada.- Revisão obsoleta retorna
409 conflict; conexão não conectada retorna404 not_found. - Calendário que some do catálogo perde destino e ocupação.
Mudar o destino vale para compromissos novos: os já vinculados continuam sincronizando no calendário original. A ocupação externa impede sobreposições sem revelar títulos nem convidados de eventos pessoais à equipe; cache ausente ou com mais de 15 minutos bloqueia novas reservas da pessoa até sincronizar.
Sincronização, tentativas e conflitos
Alterar compromisso de uma pessoa com destino conectado enfileira o envio ao Google; mudanças feitas no Google voltam pela leitura do calendário. Cada job traz id, connection_id, selection_id, appointment_id, kind (pull ou push), status (pending, processing, succeeded, failed ou conflict), attempts, last_error, available_at e updated_at, do mais recente para o mais antigo. A consulta não executa sincronizações.
POST /calendar/jobs/{id}/retry responde { "data": { "queued": true } }; job em processamento retorna 409 conflict. Quando o compromisso mudou nos dois lados, ele fica com google_conflict. Resolva com o ID do compromisso:
POST /calendar/conflicts/{appointment_id}/resolve
{ "choice": "local", "expected_revision": 4 }local mantém a versão do BotoZap e a reenvia ao Google; remote aplica título, horários, local e cancelamento do Google. Um cancelamento desfeito no Google reabre o compromisso como pending; os demais estados do BotoZap são preservados. A resposta traz o compromisso atualizado. Sem conflito, 404 not_found; revisão obsoleta, 409 conflict; conflito ainda sem a versão do Google, 422 invalid_request.
Desconectar
DELETE /calendar/connections/{id} remove a conexão, as credenciais, as seleções e a ocupação importada, e desfaz o vínculo dos compromissos com os eventos (incluindo meeting_url). Compromissos locais e eventos já criados no Google são preservados. O BotoZap também tenta revogar o acesso no Google; uma falha nessa revogação não impede a desconexão local.
Meet
Envie meeting_requested: true no compromisso para pedir um link do Google Meet. A criação é assíncrona: consulte meeting_url e o estado da sincronização depois do envio. O calendário de destino precisa aceitar conferências (conference_supported).
Erros do Google Calendar
Falhas do Google em chamadas síncronas, como /calendar/connections/{id}/refresh, retornam google_error com o status HTTP recebido do Google. Sem credenciais do Google configuradas no ambiente, a resposta é 503 not_configured. Conexão, calendário ou job de outra Conta retornam 404 not_found.
Réguas e Radar
Réguas com gatilho de agendamento podem filtrar por appointment_service_id. Remarcar reprograma o acompanhamento; cancelar ou registrar ausência interrompe os passos pendentes. Comparecimento encerra lembretes anteriores e preserva o pós-atendimento configurado com horas positivas. Consulte Réguas e consentimento.
No Radar, compromissos aparecem como entity_type: "appointment", com os motivos confirmation_pending, appointment_outcome_missing, calendar_conflict e calendar_sync_failed. Um compromisso futuro vinculado a uma oportunidade ou demanda conta como próximo passo dela.
Eventos
Criar um compromisso publica crm.appointment_created; alterá-lo publica crm.appointment_updated, com before_data e after_data. Consulte o formato em Eventos de webhook.
Os métodos existentes de SDK, CLI e MCP mantêm compatibilidade. Métodos específicos de configuração da Agenda dependem de publicação própria desses pacotes; os endpoints REST acima já estão disponíveis.
