Atendimentos
API de agendamento integrada ao chat para gerenciar agenda, profissionais, serviços e lembretes automáticos.
Licença e permissão
Atendimentos é uma funcionalidade nativa disponível em todos os planos. Qualquer membro autenticado da conta pode listar, consultar, criar, consultar disponibilidade e acessar as métricas dentro do seu escopo autorizado. Quando appointments_visibility está em own_professional, listagem, histórico de contato e métricas consideram somente a agenda do profissional vinculado ao usuário; sem vínculo, essas coleções ficam vazias. Leitura e mutações de outro profissional retornam 403. Ações em massa e exportação exigem administrador ou a permissão appointment_manage.
Credencial ausente ou inválida responde 401. Uma conta inexistente, suspensa, malformada ou inacessível ao usuário autenticado responde sempre o mesmo 404 genérico, com { "error": "Resource could not be found" }. As rotas autenticadas não possuem bloqueio nem URL de upgrade por plano.
Envelopes e erros
Respostas de recurso usam { "data": ... }; a listagem acrescenta meta. Parâmetros malformados usam { "error": "..." } com 422. Criação, PATCH e ações de status usam normalmente { "errors": { "campo": ["mensagem"] } }; uma validação propagada pelo handler global, como cancelar a partir de um estado terminal, usa { "message": "...", "attributes": ["status"] }. Uma negativa RBAC reconhecida usa { "error": "forbidden" } com 403.
Base URL
Todos os endpoints usam o prefixo /api/v1/accounts/{account_id}
Fuso de agendamento da conta
Neste guia, o fuso da conta significa: account.reporting_timezone quando válido; depois account.custom_attributes.timezone gravado pelo onboarding; e, sem uma configuração válida, America/Sao_Paulo. Booking, disponibilidade, lembretes, métricas e CSV usam essa mesma resolução. Este fallback é específico de Atendimentos; Follow-ups, Flow Builder e Sequências usam os fusos documentados no guia de Variáveis de Personalização.
Visão Geral
A API autenticada permite operar a agenda pelo dashboard, por automações server-to-server e por integrações próprias. O widget de agendamento sem autenticação possui um contrato separado na página de API pública.
Depois que a criação é confirmada no banco, somente templates ativos e entregáveis via WhatsApp cujo horário de envio ainda está no futuro materializam lembretes pendentes. O job tenta entregá-los posteriormente e procura um vínculo contact_inbox do contato com canal Channel::Whatsapp ou Channel::Api, sem filtro por uma flag active do inbox. Falhas podem deixar o lembrete pendente para nova tentativa ou marcá-lo como falho. A data e a hora usadas no texto do lembrete são renderizadas no fuso de agendamento da conta, nunca diretamente em UTC. Lembretes não fazem parte da projeção desta API.
/api/v1/accounts/{account_id}/appointmentsRetorna lista paginada de atendimentos com filtros opcionais por data, profissional, serviço, parceiro, status e contato.
Parâmetros de Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
from(query) | string (date-time) | Nao | Início em ISO 8601 estrito, com T entre data e hora; data impossível, apenas data ou separador por espaço são rejeitados. Use Z ou offset explícito. |
to(query) | string (date-time) | Nao | Fim em ISO 8601 estrito, com T entre data e hora; data impossível, apenas data ou separador por espaço são rejeitados. Use Z ou offset explícito. |
professional_id(query) | integer (int64) | Nao | Filtrar por profissional; aceita de 1 a 9223372036854775807. |
service_id(query) | integer (int64) | Nao | Filtrar por serviço; aceita de 1 a 9223372036854775807. |
partner_id(query) | integer (int64) | Nao | Filtrar por parceiro; aceita de 1 a 9223372036854775807. |
status(query) | string | Nao | scheduled | confirmed | completed | cancelled | no_show. Aceita múltiplos valores exatos separados por vírgula (ex: scheduled,confirmed). |
contact_id(query) | integer (int64) | Nao | Filtrar por contato da conta. |
pipeline_card_id(query) | integer (int64) | Nao | Filtrar por card do pipeline. |
conversation_display_id(query) | integer (int32) | Nao | Filtrar por display_id de conversa desta conta; aceita de 1 a 2147483647. |
page(query) | integer | Nao | Inteiro positivo (padrão 1). Cada página tem 50 registros; o tamanho é fixo. |
Filtros não resolvem recursos globais
Os filtros de ID são aplicados sobre a coleção já escopada à conta. Um ID válido de outra conta produz uma lista vazia, sem revelar o recurso. Valor malformado, não positivo, estruturado ou fora do limite retorna 422.
curl -s "https://chat.seudominio.com/api/v1/accounts/1/appointments?from=2027-06-01T00:00:00Z&to=2027-06-30T23:59:59Z&status=scheduled" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": [
{
"id": 42,
"cancellation_reason": null,
"currency": "BRL",
"ends_at": "2027-08-03T14:00:00.000Z",
"price_cents": 20000,
"scheduled_at": "2027-08-03T13:00:00.000Z",
"status": "scheduled",
"contact_id": 101,
"conversation_display_id": null,
"partner_id": null,
"pipeline_card_id": null,
"professional_id": 3,
"service_id": 7,
"contact": {
"id": 101,
"name": "Maria Silva",
"avatar_url": null
},
"professional": {
"id": 3,
"name": "Dr. Santos",
"specialty": "Odontologia",
"color": "#3B82F6"
},
"service": {
"id": 7,
"name": "Limpeza Dental",
"duration_minutes": 60,
"default_price_cents": 20000,
"currency": "BRL",
"color": "#2781F6"
},
"partner": null
}
],
"meta": {
"total_count": 1,
"current_page": 1,
"total_pages": 1,
"per_page": 50
}
}Summary estável e dados de contato
O index usa uma allowlist reduzida: identificadores de relacionamento, intervalo, status, preço, moeda e motivo de cancelamento, além das associações compactas. Para membros comuns, contact contém somente id, name e avatar_url. Administradores e quem possui appointment_manage recebem também email e phone_number.
/api/v1/accounts/{account_id}/appointmentsCria novo atendimento e materializa lembretes automáticos a partir dos templates do serviço.
Body (appointment)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
contact_id | integer (int64) | Sim | ID de contato desta conta, entre 1 e 9223372036854775807. |
professional_id | integer (int64) | Sim | ID de profissional desta conta, entre 1 e 9223372036854775807. |
service_id | integer (int64) | Sim | ID de serviço desta conta, entre 1 e 9223372036854775807. |
scheduled_at | string (date-time) | Sim | Início em ISO 8601 estrito, com T entre data e hora. Data impossível, apenas data ou separador por espaço são rejeitados. Sem offset, usa o fuso de agendamento da conta. |
partner_id | integer (int64) | null | Nao | Parceiro desta conta. |
ends_at | string (date-time) | null | Nao | Fim em ISO 8601 estrito, com T; data impossível, apenas data ou separador por espaço são rejeitados. Quando enviado, o intervalo customizado é validado e preservado. Se omitido/null, usa scheduled_at + duração efetiva da oferta profissional-serviço (override ou duração base). |
notes | string | null | Nao | Observações do atendimento. |
conversation_display_id | integer (int32) | null | Nao | display_id de conversa desta conta, entre 1 e 2147483647. |
pipeline_card_id | integer (int64) | null | Nao | Card existente e autorizado desta conta. |
custom_attributes | object | Nao | Atributos personalizados (JSONB) |
Envelope e referências são estritos
Envie um objeto sob appointment. Campos não suportados, IDs malformados ou datas inválidas retornam 422. Um ID numericamente válido que não exista no escopo autorizado da conta retorna 404; nenhuma referência cross-tenant é criada.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/appointments" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"appointment": {
"contact_id": 101,
"professional_id": 3,
"service_id": 7,
"scheduled_at": "2027-08-03T13:00:00Z",
"notes": "Primeira consulta"
}
}'{
"data": {
"id": 42,
"cancellation_reason": null,
"currency": "BRL",
"ends_at": "2027-08-03T14:00:00.000Z",
"price_cents": 20000,
"scheduled_at": "2027-08-03T13:00:00.000Z",
"status": "scheduled",
"contact_id": 101,
"conversation_display_id": null,
"partner_id": null,
"pipeline_card_id": null,
"professional_id": 3,
"service_id": 7,
"contact": {
"id": 101,
"name": "Maria Silva",
"avatar_url": null
},
"professional": {
"id": 3,
"name": "Dr. Santos",
"specialty": "Odontologia",
"color": "#3B82F6"
},
"service": {
"id": 7,
"name": "Limpeza Dental",
"duration_minutes": 60,
"default_price_cents": 20000,
"currency": "BRL",
"color": "#2781F6"
},
"partner": null,
"public_id": "550e8400-e29b-41d4-a716-446655440000",
"notes": "Primeira consulta",
"custom_attributes": {},
"cancelled_at": null,
"created_at": "2027-07-21T12:00:00.000Z",
"updated_at": "2027-07-21T12:00:00.000Z"
}
}Projeções explícitas e lembretes
Criação, leitura por ID, atualização e ações de status retornam a allowlist de detalhe: o summary do index mais public_id, notes, custom_attributes, cancelled_at, created_at e updated_at. Campos internos de Google, descarte e atores de auditoria não são serializados. A chave partner existe mesmo quando vale null;reminders nunca faz parte desse corpo. O contato segue a regra de PII descrita no index. Serviço, profissional e parceiro usam um escopo histórico com a semântica de with_discarded, portanto continuam aparecendo na projeção depois do arquivamento. partner pode ser null porque esse vínculo é opcional, não por o parceiro ter sido arquivado.
price_cents e currency são snapshots feitos na criação. O preço usa ProfessionalService.override_price_cents quando configurado e, sem override, Service.default_price_cents. O service.default_price_cents embutido continua sendo o valor base atual do serviço, portanto pode diferir do snapshot do atendimento.
// Data no passado:
{
"errors": {
"scheduled_at": ["must be in the future"]
}
}
// Horário ocupado (slot já reservado para o profissional):
{
"errors": {
"slot": ["time slot conflicts with an existing appointment"]
}
}
// Fora do horário de trabalho do profissional:
{
"errors": {
"slot": ["professional is not available at this time (out of hours)"]
}
}/api/v1/accounts/{account_id}/appointments/{appointment_id}Retorna detalhes de um atendimento específico com contact, professional, service e partner embutidos.
Parâmetros de Path
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
account_id(path) | integer | Sim | ID da conta |
appointment_id(path) | integer (int64) | Sim | ID entre 1 e 9223372036854775807. |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/appointments/42" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": {
"id": 42,
"cancellation_reason": null,
"currency": "BRL",
"ends_at": "2027-08-03T14:00:00.000Z",
"price_cents": 20000,
"scheduled_at": "2027-08-03T13:00:00.000Z",
"status": "scheduled",
"contact_id": 101,
"conversation_display_id": null,
"partner_id": null,
"pipeline_card_id": null,
"professional_id": 3,
"service_id": 7,
"contact": {
"id": 101,
"name": "Maria Silva",
"avatar_url": null
},
"professional": {
"id": 3,
"name": "Dr. Santos",
"specialty": "Odontologia",
"color": "#3B82F6"
},
"service": {
"id": 7,
"name": "Limpeza Dental",
"duration_minutes": 60,
"default_price_cents": 20000,
"currency": "BRL",
"color": "#2781F6"
},
"partner": null,
"public_id": "550e8400-e29b-41d4-a716-446655440000",
"notes": "Primeira consulta",
"custom_attributes": {},
"cancelled_at": null,
"created_at": "2027-07-21T12:00:00.000Z",
"updated_at": "2027-07-21T12:00:00.000Z"
}
}/api/v1/accounts/{account_id}/contacts/{contact_id}/appointment_historyRetorna o histórico paginado de atendimentos de um contato, do mais recente para o mais antigo.
Parâmetros
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
contact_id(path) | integer (int64) | Sim | Contato desta conta, entre 1 e 9223372036854775807. |
page(query) | integer | Nao | Inteiro positivo (padrão 1); cada página possui 50 registros. |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/contacts/101/appointment_history?page=1" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": [
{
"id": 42,
"public_id": "550e8400-e29b-41d4-a716-446655440000",
"service_id": 7,
"scheduled_at": "2027-08-03T13:00:00.000Z",
"ends_at": "2027-08-03T14:00:00.000Z",
"status": "scheduled",
"notes": "Primeira consulta",
"price_cents": 20000,
"currency": "BRL",
"cancelled_at": null,
"cancellation_reason": null,
"pipeline_card_id": null,
"conversation_display_id": null,
"professional": {
"id": 3,
"name": "Dr. Santos",
"color": "#3B82F6"
},
"service": {
"id": 7,
"name": "Limpeza Dental",
"duration_minutes": 60
},
"partner": null,
"created_at": "2027-07-21T12:00:00.000Z"
}
],
"meta": {
"total": 1,
"current_page": 1,
"total_pages": 1,
"per_page": 50
}
}Visibilidade e registros arquivados
O endpoint usa o mesmo policy_scope da agenda. Em own_professional, ele inclui apenas atendimentos do Professional vinculado ao usuário; sem vínculo, data fica vazio. O service_id permanece no item como snapshot do vínculo. Profissional, serviço e parceiro arquivados continuam resolvidos na projeção histórica pelo escopo with_discarded (ou sua semântica equivalente na associação). partner fica null somente quando o atendimento não possui esse vínculo opcional. Nenhum fallback é fabricado.
/api/v1/accounts/{account_id}/appointments/clientsDiretório de quem já foi agendado, agregado sobre TODO o histórico da conta.
Sem janela de datas
Diferente da listagem de agendamentos, aqui não existe recorte por período: um cliente atendido há três anos aparece, e a busca o encontra. Cada linha traz appointments_count com o total de agendamentos do cliente, mais last_appointment_at (última visita passada) e next_appointment_at (próxima visita futura).
Agendamento cancelado ou marcado como no-show conta no total, mas nunca alimenta as duas datas: quem cancelou não foi atendido, e quem faltou não volta naquele horário. partner_name é o parceiro do agendamento mais recente do cliente.
Toda agregação parte do policy_scope de agendamentos, não da conta: no modo own_professional, o agente vê somente os clientes do Professional vinculado a ele, e appointments_counte as duas datas contam apenas os agendamentos que ele pode ver — um contato compartilhado com outro profissional não vaza o volume desse outro.
Os campos de contato seguem a mesma regra da listagem: email e phone_number só aparecem para quem pode gerenciar agendamentos. O CPF do contato nunca é retornado.
Parâmetros de Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
q(query) | string | Nao | Busca sem distinção de maiúsculas, até 120 caracteres. % e _ são tratados como literais. Quem pode gerenciar agendamentos busca por nome, e-mail e telefone; quem não pode busca somente por nome — buscar num campo que a resposta esconde devolveria o dado pelo resultado, permitindo adivinhar um telefone com o total_count confirmando cada palpite. |
sort(query) | string | Nao | recent (padrão): última visita mais recente primeiro. upcoming: próxima visita mais próxima. frequency: mais agendamentos. name: alfabético. Em toda ordenação por data, quem não tem a data vai para o fim. Valor não suportado retorna 422. |
page(query) | integer | Nao | Página, 30 clientes por página. Padrão 1. |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/appointments/clients?sort=frequency" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": [
{
"id": 21,
"name": "Sabrina Oliveira",
"email": null,
"phone_number": "+5511988100020",
"avatar_url": null,
"partner_name": "Unimed",
"appointments_count": 18,
"last_appointment_at": "2026-06-08T08:00:00Z",
"next_appointment_at": "2027-06-15T13:00:00Z"
}
],
"meta": {
"total_count": 11,
"current_page": 1,
"total_pages": 1,
"per_page": 30
}
}/api/v1/accounts/{account_id}/appointments/{appointment_id}Atualiza campos do atendimento. Para mudar status use os endpoints de ação (confirm, complete, no_show).
Campos atualizáveis são limitados
O PATCH só permite scheduled_at, notes, partner_id e custom_attributes. ends_at não é aceito no PATCH: ao reagendar, o horário final é recalculado pelo backend a partir da duração efetiva da oferta profissional-serviço. Também não é possível trocar professional_id, service_id ou contact_id — esses campos são definidos apenas na criação. Para mudar status use os endpoints de ação (/confirm, /complete, /no_show).
Body (appointment)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
scheduled_at | string (date-time) | Nao | Reagendar em ISO 8601 estrito, com T entre data e hora. Data impossível, apenas data ou separador por espaço retornam 422; sem offset, usa o fuso de agendamento da conta. Conflito de slot também retorna 422. |
partner_id | integer (int64) | null | Nao | Trocar ou remover o parceiro desta conta. |
notes | string | null | Nao | Atualizar ou limpar as observações. |
custom_attributes | object | Nao | Atributos personalizados (JSONB) |
curl -X PATCH "https://chat.seudominio.com/api/v1/accounts/1/appointments/42" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "appointment": { "scheduled_at": "2027-08-04T14:00:00Z", "notes": "Reagendado a pedido da paciente" } }'{
"data": {
"id": 42,
"cancellation_reason": null,
"currency": "BRL",
"ends_at": "2027-08-04T15:00:00.000Z",
"price_cents": 20000,
"scheduled_at": "2027-08-04T14:00:00.000Z",
"status": "scheduled",
"contact_id": 101,
"conversation_display_id": null,
"partner_id": null,
"pipeline_card_id": null,
"professional_id": 3,
"service_id": 7,
"contact": {
"id": 101,
"name": "Maria Silva",
"avatar_url": null
},
"professional": {
"id": 3,
"name": "Dr. Santos",
"specialty": "Odontologia",
"color": "#3B82F6"
},
"service": {
"id": 7,
"name": "Limpeza Dental",
"duration_minutes": 60,
"default_price_cents": 20000,
"currency": "BRL",
"color": "#2781F6"
},
"partner": null,
"public_id": "550e8400-e29b-41d4-a716-446655440000",
"notes": "Reagendado a pedido da paciente",
"custom_attributes": {},
"cancelled_at": null,
"created_at": "2027-07-21T12:00:00.000Z",
"updated_at": "2027-07-21T12:00:00.000Z"
}
}/api/v1/accounts/{account_id}/appointments/{appointment_id}Cancela o atendimento sem remover o registro. Repetir o DELETE em um atendimento já cancelled é idempotente e retorna 204.
Escopo de conta
O appointment_id é resolvido dentro da conta autenticada (Current.account.appointments.find). Não é um ID global — tentar cancelar um atendimento de outra conta retorna 404.
Parâmetros de Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
reason(query) | string | Nao | Motivo do cancelamento |
curl -X DELETE "https://chat.seudominio.com/api/v1/accounts/1/appointments/42?reason=Paciente%20solicitou%20via%20WhatsApp" \
-H "api_access_token: YOUR_TOKEN"/api/v1/accounts/{account_id}/appointments/{appointment_id}/confirmTransiciona scheduled para confirmed. Repetir em confirmed é idempotente e retorna 200; estados terminais retornam 422.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/appointments/42/confirm" \
-H "api_access_token: YOUR_TOKEN"{
"data": {
"id": 42,
"cancellation_reason": null,
"currency": "BRL",
"ends_at": "2027-08-03T14:00:00.000Z",
"price_cents": 20000,
"scheduled_at": "2027-08-03T13:00:00.000Z",
"status": "confirmed",
"contact_id": 101,
"conversation_display_id": null,
"partner_id": null,
"pipeline_card_id": null,
"professional_id": 3,
"service_id": 7,
"contact": {
"id": 101,
"name": "Maria Silva",
"avatar_url": null
},
"professional": {
"id": 3,
"name": "Dr. Santos",
"specialty": "Odontologia",
"color": "#3B82F6"
},
"service": {
"id": 7,
"name": "Limpeza Dental",
"duration_minutes": 60,
"default_price_cents": 20000,
"currency": "BRL",
"color": "#2781F6"
},
"partner": null,
"public_id": "550e8400-e29b-41d4-a716-446655440000",
"notes": "Primeira consulta",
"custom_attributes": {},
"cancelled_at": null,
"created_at": "2027-07-21T12:00:00.000Z",
"updated_at": "2027-07-21T12:00:00.000Z"
}
}/api/v1/accounts/{account_id}/appointments/{appointment_id}/completeTransiciona scheduled ou confirmed para completed. Não aceita body; repetir em completed é idempotente e retorna 200.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/appointments/42/complete" \
-H "api_access_token: YOUR_TOKEN"{
"data": {
"id": 42,
"cancellation_reason": null,
"currency": "BRL",
"ends_at": "2027-08-03T14:00:00.000Z",
"price_cents": 20000,
"scheduled_at": "2027-08-03T13:00:00.000Z",
"status": "completed",
"contact_id": 101,
"conversation_display_id": null,
"partner_id": null,
"pipeline_card_id": null,
"professional_id": 3,
"service_id": 7,
"contact": {
"id": 101,
"name": "Maria Silva",
"avatar_url": null
},
"professional": {
"id": 3,
"name": "Dr. Santos",
"specialty": "Odontologia",
"color": "#3B82F6"
},
"service": {
"id": 7,
"name": "Limpeza Dental",
"duration_minutes": 60,
"default_price_cents": 20000,
"currency": "BRL",
"color": "#2781F6"
},
"partner": null,
"public_id": "550e8400-e29b-41d4-a716-446655440000",
"notes": "Primeira consulta",
"custom_attributes": {},
"cancelled_at": null,
"created_at": "2027-07-21T12:00:00.000Z",
"updated_at": "2027-07-21T12:00:00.000Z"
}
}/api/v1/accounts/{account_id}/appointments/{appointment_id}/no_showTransiciona scheduled ou confirmed para no_show. Não aceita body; repetir em no_show é idempotente e retorna 200.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/appointments/42/no_show" \
-H "api_access_token: YOUR_TOKEN"{
"data": {
"id": 42,
"cancellation_reason": null,
"currency": "BRL",
"ends_at": "2027-08-03T14:00:00.000Z",
"price_cents": 20000,
"scheduled_at": "2027-08-03T13:00:00.000Z",
"status": "no_show",
"contact_id": 101,
"conversation_display_id": null,
"partner_id": null,
"pipeline_card_id": null,
"professional_id": 3,
"service_id": 7,
"contact": {
"id": 101,
"name": "Maria Silva",
"avatar_url": null
},
"professional": {
"id": 3,
"name": "Dr. Santos",
"specialty": "Odontologia",
"color": "#3B82F6"
},
"service": {
"id": 7,
"name": "Limpeza Dental",
"duration_minutes": 60,
"default_price_cents": 20000,
"currency": "BRL",
"color": "#2781F6"
},
"partner": null,
"public_id": "550e8400-e29b-41d4-a716-446655440000",
"notes": "Primeira consulta",
"custom_attributes": {},
"cancelled_at": null,
"created_at": "2027-07-21T12:00:00.000Z",
"updated_at": "2027-07-21T12:00:00.000Z"
}
}/api/v1/accounts/{account_id}/appointments/availabilityRetorna slots disponíveis para um profissional em uma data, considerando horário de trabalho, buffer e consultas existentes.
Parâmetros de Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
professional_id(query) | integer (int64) | Sim | Profissional desta conta; aceita de 1 a 9223372036854775807. |
date(query) | string (date) | Sim | Data estrita em YYYY-MM-DD. |
service_id(query) | integer (int64) | Nao | Serviço desta conta oferecido pelo profissional; usa o override de duração da oferta quando configurado. |
duration_minutes(query) | integer (int32) | Nao | Duração entre 1 e 2147483647 quando service_id não for informado (padrão 60). Se enviado com service_id, ainda é validado, mas a duração efetiva da oferta prevalece. |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/appointments/availability?date=2027-08-03&professional_id=3&service_id=7" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": {
"date": "2027-08-03",
"professional_id": 3,
"slots": [
"2027-08-03T08:00:00-03:00",
"2027-08-03T08:15:00-03:00",
"2027-08-03T08:30:00-03:00"
]
}
}Slots já conflitantes não aparecem
A resposta lista apenas os horários de início disponíveis (array de timestamps ISO 8601). Horários ocupados são omitidos — não há campo available: false. A grade avança em 15 minutos e usa o fuso de agendamento da conta; duração, buffer, horário de trabalho, compromissos existentes e horários já passados participam do cálculo.
Com service_id, a duração usada é o override_duration_minutes da oferta desse profissional, quando existir; caso contrário, vale service.duration_minutes.
/api/v1/accounts/{account_id}/appointments/availability_rangeMesma resposta de /availability, para todos os dias entre from e to, em uma requisição só.
Parâmetros de Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
professional_id(query) | integer (int64) | Sim | Profissional desta conta; aceita de 1 a 9223372036854775807. |
from(query) | string (date) | Sim | Primeiro dia do intervalo, em YYYY-MM-DD, inclusive. |
to(query) | string (date) | Sim | Último dia do intervalo, em YYYY-MM-DD, inclusive. Não pode ser anterior a from, e o intervalo tem no máximo 42 dias. |
service_id(query) | integer (int64) | Nao | Serviço desta conta oferecido pelo profissional; usa o override de duração da oferta quando configurado. |
duration_minutes(query) | integer (int32) | Nao | Duração entre 1 e 2147483647 quando service_id não for informado (padrão 60). |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/appointments/availability_range?from=2027-08-02&to=2027-08-08&professional_id=3&service_id=7" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": {
"professional_id": 3,
"duration_minutes": 60,
"days": [
{
"date": "2027-08-02",
"slots": [
"2027-08-02T08:00:00-03:00",
"2027-08-02T08:15:00-03:00"
]
},
{
"date": "2027-08-03",
"slots": []
}
]
}
}Todo dia do intervalo vem na resposta
As regras são exatamente as de /availability — horário de trabalho, buffer, duração do serviço, compromissos existentes e horários já passados — aplicadas dia a dia. O que muda é o número de requisições: desenhar uma semana pediria sete chamadas, e um mês, quarenta e duas.
Dias em que o profissional não atende aparecem com slots vazio, em vez de serem omitidos: um dia ausente seria indistinguível de um dia sem horário livre, e quem chama não teria como separar uma falha na resposta de uma agenda fechada.
/api/v1/accounts/{account_id}/appointments/available_professionalsLista profissionais ativos que podem atender um intervalo específico, considerando horário de trabalho, duração efetiva, buffer e conflitos.
Parâmetros de Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
scheduled_at(query) | string (date-time) | Sim | Início em ISO 8601 estrito, com T entre data e hora. Data impossível, apenas data ou separador por espaço são rejeitados; sem offset, usa o fuso de agendamento da conta. |
service_id(query) | integer (int64) | Nao | Serviço desta conta; filtra profissionais ativos que o oferecem e aplica a duração efetiva de cada oferta. |
duration_minutes(query) | integer (int32) | Nao | Duração de 1 a 2147483647 quando service_id é omitido (padrão 60). Se enviado com service_id, ainda é validado. |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/appointments/available_professionals?scheduled_at=2027-08-05T10:00:00-03:00&service_id=7" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": {
"scheduled_at": "2027-08-05T10:00:00-03:00",
"ends_at": "2027-08-05T11:00:00-03:00",
"duration_minutes": 60,
"service_id": 7,
"total_professionals": 2,
"available_count": 1,
"professionals": [
{
"id": 3,
"name": "Dr. Santos",
"specialty": "Odontologia",
"color": "#3B82F6",
"service_ids": [7]
}
]
}
}Conflitos considerados
Atendimentos cancelled e no_show não bloqueiam o intervalo. Com service_id, apenas profissionais ativos que oferecem esse serviço entram no conjunto avaliado. O buffer é aplicado antes e depois dos compromissos.
duration_minutes e ends_at no nível superior continuam descrevendo a duração base do serviço (ou o parâmetro/default quando não há serviço). Entretanto, cada candidato é testado com sua própria duração efetiva: um override pode ampliar ou reduzir a janela avaliada. Confirme os slots do profissional escolhido em /appointments/availability antes de criar o atendimento.
/api/v1/accounts/{account_id}/appointments/metricsRetorna todas as métricas produzidas pelo relatório de atendimentos para um intervalo efetivo de até um ano.
Janela efetiva e fuso
Sem parâmetros, from usa o início do dia de 30 dias atrás e to usa o fim do dia atual, ambos no fuso de agendamento da conta. Se o intervalo solicitado exceder um ano, o backend avança from para um ano antes de to. data.range informa os timestamps efetivamente usados; os dois limites são inclusivos. by_day e by_day_status agrupam pelo mesmo fuso da conta.
A agregação parte do mesmo policy_scope da listagem. No modo own_professional, um agente vê somente suas contagens, receitas e agrupamentos; sem Professional vinculado, recebe métricas zeradas. Dados de outros profissionais nunca entram no resultado restrito.
Parâmetros de Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
from(query) | string (date-time) | Nao | Início ISO 8601 estrito, com T; data impossível, apenas data ou espaço são rejeitados. Offset/Z preserva o instante; sem offset, usa o fuso da conta. Padrão: início do dia de 30 dias atrás nesse fuso. |
to(query) | string (date-time) | Nao | Fim ISO 8601 estrito, com T; data impossível, apenas data ou espaço são rejeitados. Offset/Z preserva o instante; sem offset, usa o fuso da conta. Padrão: fim do dia atual nesse fuso. |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/appointments/metrics?from=2027-08-01T00:00:00&to=2027-08-31T23:59:59" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": {
"total_count": 4,
"by_status": {
"scheduled": 1,
"confirmed": 0,
"completed": 2,
"cancelled": 0,
"no_show": 1
},
"completed_count": 2,
"no_show_count": 1,
"no_show_rate": 33.33,
"revenue_realized_cents": 40000,
"revenue_forecast_cents": 20000,
"by_professional": [
{ "id": 3, "name": "Dr. Santos", "count": 4, "revenue_cents": 60000 }
],
"by_service": [
{ "id": 7, "name": "Limpeza Dental", "count": 4, "revenue_cents": 60000 }
],
"by_day": [
{ "date": "2027-08-02", "count": 2 },
{ "date": "2027-08-03", "count": 2 }
],
"by_day_status": [
{ "date": "2027-08-02", "scheduled": 0, "confirmed": 0, "completed": 1, "cancelled": 0, "no_show": 1 },
{ "date": "2027-08-03", "scheduled": 1, "confirmed": 0, "completed": 1, "cancelled": 0, "no_show": 0 }
],
"range": {
"from": "2027-08-01T00:00:00-03:00",
"to": "2027-08-31T23:59:59-03:00"
}
}
}Como as métricas são calculadas
revenue_realized_cents soma o preço de atendimentos completed;revenue_forecast_cents soma scheduled e confirmed. A taxa de no-show usa como denominador apenas atendimentos finalizados: completed + no_show + cancelled. Os agrupamentos por profissional e serviço retornam id, name, count e revenue_cents.
{ "error": "invalid date" }/api/v1/accounts/{account_id}/appointments/bulk_actionAplica uma ação de status a múltiplos atendimentos simultaneamente.
Escopo de conta e ações suportadas
Todos os IDs precisam existir no escopo autorizado. Se um único ID válido estiver ausente, pertencer a outra conta ou ficar fora da visibilidade do usuário, a API retorna 404 antes de aplicar qualquer ação. O lote não ignora IDs silenciosamente.
Ações suportadas: confirm, cancel, no_show. Não existe ação complete em massa — qualquer outro valor retorna 422.
Use bulk_action, não action
O verbo viaja sob a chave bulk_action — a chave action é reservada pelo roteamento do Rails e seria ignorada. Cada atendimento é processado de forma independente depois que todos os IDs foram resolvidos: uma transição de status inválida em um registro não derruba os demais e aparece em failed. O 200 só se aplica a lotes estruturalmente válidos e inteiramente resolvidos.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
ids | array[integer (int64)] | Sim | De 1 a 100 IDs positivos, únicos e dentro do limite bigint. |
bulk_action | string | Sim | confirm | cancel | no_show |
reason | string | null | Nao | Motivo usado somente quando bulk_action=cancel. |
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/appointments/bulk_action" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "ids": [42, 43, 44], "bulk_action": "confirm" }'{
"data": {
"action": "confirm",
"count": 2,
"succeeded": [42, 43],
"failed": [
{ "id": 44, "errors": { "status": ["cannot transition from cancelled to confirmed"] } }
]
}
}/api/v1/accounts/{account_id}/appointments/export.csvExporta os atendimentos em CSV (anexo), com horários no fuso de agendamento da conta. Endpoint privilegiado (admin ou custom role appointment_manage). Respeita os mesmos filtros do index (from, to, professional_id, service_id, partner_id, status, contact_id, pipeline_card_id, conversation_display_id).
ID,Status,Scheduled at,Ends at,Contact,Professional,Service,Price,Currency,Cancellation reason
42,confirmed,2027-06-15T09:00:00-03:00,2027-06-15T10:00:00-03:00,Maria Souza,Dr Joao,Limpeza,150.0,BRL,Fuso dos filtros e do arquivo
from e to exigem ISO 8601 estrito com T entre data e hora; data impossível, apenas data ou separador por espaço retornam 422. Um offset/Z preserva o instante e um valor sem offset usa o fuso da conta; os dois limites são inclusivos. Scheduled at e Ends at são convertidos para esse fuso antes de entrar no CSV; a data do nome do arquivo também é a data local da conta.
/api/v1/accounts/{account_id}/appointments/{appointment_id}/sync_to_googleRota reservada para compatibilidade futura. A sincronização individual ainda não está implementada.
Não trate esta rota como sucesso
Depois de autenticar, escopar a conta e autorizar o atendimento, a rota sempre retorna 501. Ela não cria nem atualiza um evento no Google Calendar.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/appointments/42/sync_to_google" \
-H "api_access_token: YOUR_TOKEN"{ "error": "Google Calendar sync not yet implemented" }Transições de Status
O modelo Appointment implementa um guard de transição de status. Apenas as transições listadas abaixo são permitidas. Os endpoints confirm, complete e no_show retornam 422 com:
| Status atual | Pode transicionar para |
|---|---|
scheduled | confirmed, cancelled, no_show, completed |
confirmed | completed, cancelled, no_show |
completed | — (terminal) |
cancelled | — (terminal) |
no_show | — (terminal) |
Endpoints de ação
Use os endpoints dedicados /confirm, /complete, /no_show para transicionar status. O endpoint PATCH só atualiza campos de dados (scheduled_at, notes, partner_id, custom_attributes). Repetir uma ação quando o atendimento já está no status de destino preserva o estado e retorna 200. No DELETE de cancelamento, uma transição inválida também retorna 422, mas pelo envelope global message + attributes descrito no início da página.