Files
KLINGO-CLINICAL-SYSLIFE-GCHAT/gti-scheduling-mcp/docs/zpro-api-mapping.md
T
GustavoandClaude Sonnet 5 257afe98c0 Snapshot: gti-scheduling-mcp (Z-PRO + Klingo adapters) e workflow n8n de agendamento funcionando de ponta a ponta
- 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>
2026-08-10 17:23:10 -03:00

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 body raw/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

  1. Formato de sucesso de qualquer endpoint (envelope da resposta, nomes de campos, se há paginação e como ela é representada).
  2. Formato de erro de qualquer endpoint (estrutura do corpo de erro, nomes de campos como message/error/code).
  3. Identificador retornado após a criação do agendamento (nome do campo, ex.: id vs _id vs appointment.id).
  4. Conceito de "horário disponível": a pasta não expõe nenhum endpoint de disponibilidade/slots livres. AppointmentList lista 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.
  5. Relação entre slotId (tipo genérico do projeto, CreateAppointmentInput.slotId) e a API Z-PRO: AppointmentCreate não recebe um identificador de horário/slot — o agendamento é criado diretamente por startAt/endAt. Não há, na coleção, nenhum conceito de "slot" com identificador próprio.
  6. Campos obrigatórios vs. opcionais no corpo de AppointmentCreate (a coleção só mostra um exemplo, sem schema declarado).
  7. 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 para AppointmentList (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ão appointment.id).
  • data.status reflete 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 no ZProAdapter é feito por analogia com o valor documentado no request (AppointmentList's filtro de status), não por observação direta.
  • data.startAt/data.endAt são ecoados exatamente como enviados.
  • data.contactId continua null quando 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.