- gti-scheduling-mcp: MCP server com ZProAdapter e KlingoAdapter (agendar() completo pros dois) - Workflow n8n 'Agendamento - GChat - Z-PRO': webhook -> AI Agent -> MCP -> Z-PRO, testado com sucesso real - Documentacao: specs, plano, runbook de problemas reais e export do workflow Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
14 KiB
Mapeamento da API Z-PRO — pasta "Agendamentos e Lembretes"
Este documento contém apenas informações verificadas diretamente na coleção pública do Postman em https://www.postman.com/comunidade-zdg/z-pro/folder/ktt2uon/agendamentos-e-lembretes (coleção "Z-PRO v4.x.x.x", pasta "📅 Agendamentos e Lembretes"). Nenhum valor de token, header sensível ou segredo real foi copiado.
Status: análise incompleta. A integração real (
ZProAdapter) não foi implementada nesta etapa porque partes essenciais do contrato não estão disponíveis na coleção. Veja a seção "O que não foi possível verificar".
Itens existentes na pasta
A pasta contém 10 requests. Apenas os relacionados ao escopo atual (consultar agendamentos / criar agendamento) foram analisados em detalhe; os demais foram apenas identificados para não serem confundidos com o escopo.
| Nome | Método | Path | No escopo atual? |
|---|---|---|---|
| AppointmentCreate | POST | /v2/api/external/{{ApiID}}/appointment/create |
✅ sim |
| AppointmentList | GET | /v2/api/external/{{ApiID}}/appointment/list |
✅ sim (única consulta existente — ver ressalva abaixo) |
| AppointmentShow | GET | /v2/api/external/{{ApiID}}/appointment/show/{{id}} |
fora do escopo pedido (não é "consultar horários/agendamentos disponíveis" em lista) |
| AppointmentUpdate | POST | /v2/api/external/{{ApiID}}/appointment/update/{{id}} |
❌ não (remarcação — explicitamente fora do escopo) |
| AppointmentDelete | POST | /v2/api/external/{{ApiID}}/appointment/delete/{{id}} |
❌ não (cancelamento — explicitamente fora do escopo) |
| ScheduleReminderCreate | POST | /v2/api/external/{{ApiID}}/scheduleReminder/create |
❌ não (lembretes — explicitamente fora do escopo) |
| ScheduleReminderList | GET | /v2/api/external/{{ApiID}}/scheduleReminder/list |
❌ não |
| ScheduleReminderToggle | POST | /v2/api/external/{{ApiID}}/scheduleReminder/toggle/{{id}} |
❌ não |
| ScheduleReminderUpdate | POST | /v2/api/external/{{ApiID}}/scheduleReminder/update/{{id}} |
❌ não |
| ScheduleReminderDelete | POST | /v2/api/external/{{ApiID}}/scheduleReminder/delete/{{id}} |
❌ não |
Ressalva importante: a pasta não possui nenhum endpoint de "horários
disponíveis" (busca de vagas livres/slots). O único endpoint de consulta
relacionado a agendamentos é AppointmentList, que lista agendamentos já
existentes (filtráveis por status pending | confirmed | cancelled | completed), não horários livres para agendar. Ver detalhes na seção de
ambiguidades.
Autenticação
- Tipo: Bearer Token (
auth.type = "bearer"), definido tanto no nível da coleção quanto em cada request individual. - O token é enviado automaticamente pelo Postman no header
Authorization: Bearer <token>. - Variável de coleção que armazena o token:
BearerToken(valor de exemplo na coleção:your_token_here— placeholder, não é um token real). - Não há chave de API adicional em header (ex.:
X-API-Key) documentada nestes requests — apenas o Bearer token.
Variáveis de coleção relevantes
| Variável | Uso verificado |
|---|---|
BaseUrl |
Host base da API (ex.: https://{{BaseUrl}}/v2/api/external/...) |
ApiID |
Segmento obrigatório da URL em todos os endpoints desta pasta: /v2/api/external/{{ApiID}}/.... Não há descrição do que ele representa (ex.: id da instância/empresa) além do nome da variável. |
BearerToken |
Token usado na autenticação Bearer |
SuperAdminToken |
Não utilizado em nenhum request desta pasta |
Achado relevante para configuração: o ApiID é parte obrigatória da
URL de todo endpoint. A configuração atual do projeto
(src/config/config.types.ts, src/config/env.config.ts) já prevê
ZPRO_INSTANCE_ID como opcional. Se ApiID corresponder a esse campo,
ele precisa passar a ser obrigatório — mas isso não deve ser alterado
sem confirmação, pois não há descrição na coleção que confirme a
equivalência semântica entre ApiID e "instância".
AppointmentCreate (criar agendamento)
- Método:
POST - URL:
https://{{BaseUrl}}/v2/api/external/{{ApiID}}/appointment/create - Auth: Bearer (herdado da coleção)
- Headers explícitos no request: nenhum além dos automáticos do Postman
(Bearer +
Content-Type: application/json, implícito pelo modo de bodyraw/json) - Query params: nenhum
- Corpo da requisição (JSON,
dataMode: raw, exemplo salvo na coleção):
{
"title": "Reunião de Vendas",
"description": "Reunião sobre proposta comercial",
"contactId": "{{contactId}}",
"contactName": "João Silva",
"contactPhone": "5511999999999",
"whatsappId": "{{whatsappId}}",
"startAt": "2026-04-15T14:00:00.000Z",
"endAt": "2026-04-15T15:00:00.000Z",
"status": "pending",
"notes": "Levar proposta impressa"
}
- Campos identificados no exemplo:
title,description,contactId,contactName,contactPhone,whatsappId,startAt,endAt,status,notes. - Não há indicação, na coleção, de quais desses campos são obrigatórios versus opcionais (o Postman não declara isso fora do exemplo).
- Formato de sucesso, formato de erro e o campo que identifica o
agendamento criado (
id,_id,appointment.id, etc.): não verificados — não há nenhuma resposta de exemplo salva neste request na coleção (responses_order: []).
AppointmentList (única consulta existente no escopo)
- Método:
GET - URL:
https://{{BaseUrl}}/v2/api/external/{{ApiID}}/appointment/list - Auth: Bearer (herdado da coleção)
- Query params verificados:
| Parâmetro | Exemplo | Habilitado por padrão | Descrição na coleção |
|---|---|---|---|
page |
1 |
sim | — |
limit |
20 |
sim | — |
status |
pending |
sim | pending | confirmed | cancelled | completed |
startFrom |
2026-04-01T00:00:00.000Z |
não (disabled) | Filtrar agendamentos a partir desta data (ISO 8601) |
startTo |
2026-04-30T23:59:59.000Z |
não (disabled) | Filtrar agendamentos até esta data (ISO 8601) |
search |
"" |
não (disabled) | Busca por título ou nome do contato |
- Corpo da requisição: nenhum (GET).
- Formato de sucesso (envelope, paginação, forma de cada item da lista) e formato de erro: não verificados — sem resposta de exemplo salva.
O que não foi possível verificar
- Formato de sucesso de qualquer endpoint (envelope da resposta, nomes de campos, se há paginação e como ela é representada).
- Formato de erro de qualquer endpoint (estrutura do corpo de erro,
nomes de campos como
message/error/code). - Identificador retornado após a criação do agendamento (nome do
campo, ex.:
idvs_idvsappointment.id). - Conceito de "horário disponível": a pasta não expõe nenhum endpoint
de disponibilidade/slots livres.
AppointmentListlista agendamentos já existentes (ocupados), não horários livres. Não é seguro tratar a ausência de um agendamento como "horário disponível" — isso seria inventar uma regra de negócio não documentada. - Relação entre
slotId(tipo genérico do projeto,CreateAppointmentInput.slotId) e a API Z-PRO:AppointmentCreatenão recebe um identificador de horário/slot — o agendamento é criado diretamente porstartAt/endAt. Não há, na coleção, nenhum conceito de "slot" com identificador próprio. - Campos obrigatórios vs. opcionais no corpo de
AppointmentCreate(a coleção só mostra um exemplo, sem schema declarado). - Semântica exata de
ApiID(é o identificador da instância? da empresa? do WhatsApp conectado?) — não há descrição na coleção.
O que é necessário para prosseguir com a integração real
Qualquer um dos itens abaixo destravaria a implementação:
- Um exemplo de resposta salvo (Postman "Save Response" / "Add
Example") para
AppointmentCreate(sucesso e, se possível, erro) e paraAppointmentList(sucesso). - Confirmação explícita de que não existe endpoint de horários/slots
disponíveis na API Z-PRO (para então documentar oficialmente que
consultarHorarios()não tem correspondência 1:1 e decidir, com o responsável do produto, qual comportamento é aceitável — por exemplo, reinterpretar o escopo, ou aguardar um endpoint futuro). - Documentação (Swagger/OpenAPI, README da API, ou outra coleção) que descreva o schema de resposta dos dois endpoints.
- Confirmação do significado e da obrigatoriedade de
ApiID.
Decisão tomada nesta etapa
Como os contratos de resposta não puderam ser verificados, o
ZProAdapter não foi alterado — permanece lançando Not implemented,
exatamente como estava antes desta análise, para não implementar por
suposição. npm run build, npm run lint e npm test continuam
passando sem alterações de código.
Atualização — resposta real de sucesso capturada (teste manual controlado)
Em 09/08/2026, foi executado um teste manual único e controlado de
POST /v2/api/external/{ApiID}/appointment/create, contra uma instância
real do Z-PRO, com dados fictícios (TESTE-MCP-2026-04-20-001). O
agendamento foi criado de fato (confirmado visualmente na interface do
Z-PRO) e a resposta HTTP foi capturada:
- Content-Type:
application/json; charset=utf-8 - Corpo bruto (dados fictícios, sem nenhuma credencial):
{
"success": true,
"data": {
"source": "manual",
"checkInStatus": "none",
"id": 2,
"title": "TESTE-MCP-2026-04-20-001",
"description": null,
"contactId": null,
"contactName": "Paciente Teste MCP",
"contactPhone": "5511900000000",
"whatsappId": null,
"tenantId": 4,
"startAt": "2026-04-20T14:00:00.000Z",
"endAt": "2026-04-20T14:30:00.000Z",
"status": "pending",
"notes": null,
"updatedAt": "2026-08-09T20:32:06.426Z",
"createdAt": "2026-08-09T20:32:06.426Z",
"bookingPageId": null,
"bookingEventTypeId": null,
"bookingProfessionalId": null,
"userId": null,
"location": null,
"meetingLink": null,
"formAnswers": null,
"cancelToken": null,
"googleEventId": null,
"checkInSentAt": null
}
}
O que isso confirma:
- Envelope de sucesso:
{ success: boolean, data: {...} }. - Identificador do agendamento criado:
data.id, numérico (não_id, nãoappointment.id). data.statusreflete o mesmo valor enviado na requisição ("pending") — não temos, ainda, um exemplo real de"confirmed"ou"cancelled"retornados pela API; o mapeamento desses dois valores noZProAdapteré feito por analogia com o valor documentado no request (AppointmentList's filtro destatus), não por observação direta.data.startAt/data.endAtsão ecoados exatamente como enviados.data.contactIdcontinuanullquando não enviado no request (não inventa um contato).
O que continua não verificado: o formato de erro (4xx/5xx) do
endpoint (esse teste só cobriu o caminho de sucesso), e os valores reais
de data.status para "confirmed"/"cancelled". O ZProAdapter
continua tratando qualquer HTTP 2xx como sucesso mesmo que o corpo
não bata com este envelope exato (ver zpro.mapper.ts), como proteção
contra duplicidade caso a API mude de formato em algum caso não coberto
por este teste único.