openapi: 3.1.0 info: title: TopTalking API version: "1.1.0" summary: API da TopTalking para WhatsApp, contatos, Kanban, Agenda e webhooks. description: | A API da TopTalking deixa o seu sistema (ou a sua IA) conversar com o WhatsApp conectado na sua conta: enviar mensagens, buscar e criar contatos, ler o histórico de conversas, mover cards no Kanban, agendar mensagens e receber eventos por webhook. **Autenticação:** envie a chave no cabeçalho `Authorization: Bearer tt_live_...`. Gere a chave no painel em Configurações → Desenvolvedor. **Nunca use a chave no navegador.** A API não aceita chamadas de front-end (CORS bloqueado de propósito): chame a partir do seu servidor, de uma edge function ou de uma automação. **Em toda resposta:** cabeçalho `X-Request-Id` (cite-o ao pedir suporte) e os cabeçalhos `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`. Qualquer rota pode responder `401` (chave), `429` (limite por minuto, com `Retry-After`), `500`, `502`, `503` e `504`; toda rota de escrita pode responder `403` (chave somente leitura). Documentação completa: https://platform.toptalking.com.br Para IAs: https://platform.toptalking.com.br/llms.txt contact: name: TopTalking url: https://platform.toptalking.com.br servers: - url: https://platform.toptalking.com.br/v1 description: Produção security: - bearerAuth: [] tags: - name: Conta - name: Canais description: Números de WhatsApp e Chat do site conectados à conta. - name: Contatos description: Contatos são as conversas da caixa de entrada (um por número e canal). - name: Mensagens - name: Kanban description: Etapas do Kanban (pipeline) da organização. - name: Mensagens agendadas description: Mensagens programadas, as mesmas da Agenda do painel. - name: Webhooks description: Endpoints que recebem eventos da sua conta. - name: Grupos description: Grupos do WhatsApp (cada grupo também é uma conversa). - name: Equipe description: Membros da organização (atendentes e administradores). - name: Agentes de IA description: Agentes de IA que respondem as conversas. - name: Campanhas description: Disparos em massa pelo WhatsApp Oficial (API da Meta) com templates aprovados. paths: /me: get: tags: [Conta] operationId: getMe summary: Dados da chave e da organização description: Útil para testar se a chave funciona. responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Me" } "401": { $ref: "#/components/responses/Unauthorized" } "429": { $ref: "#/components/responses/RateLimited" } /channels: get: tags: [Canais] operationId: listChannels summary: Listar canais responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/Channel" } "401": { $ref: "#/components/responses/Unauthorized" } post: tags: [Canais] operationId: createChannel summary: Conectar um novo WhatsApp (QR Code) description: | Cria um canal de WhatsApp e já devolve o QR Code para ler no celular (WhatsApp → Aparelhos conectados → Conectar um aparelho). Depois, consulte `GET /channels/{channel_id}/qr` até `status` = `connected`. Respeita o limite de conexões do plano da organização (`422 limit_reached`). parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: false content: application/json: schema: { $ref: "#/components/schemas/ChannelCreate" } example: { name: Vendas } responses: "201": description: Canal criado, aguardando a leitura do QR Code. content: application/json: schema: { $ref: "#/components/schemas/ChannelConnection" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "409": { $ref: "#/components/responses/Conflict" } "422": description: "Limite de conexões do plano atingido (`limit_reached`)." content: application/json: schema: { $ref: "#/components/schemas/Error" } "502": { $ref: "#/components/responses/ProviderError" } /channels/{channel_id}: get: tags: [Canais] operationId: getChannel summary: Buscar canal parameters: - $ref: "#/components/parameters/ChannelId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Channel" } "404": { $ref: "#/components/responses/NotFound" } patch: tags: [Canais] operationId: updateChannel summary: Renomear canal parameters: - $ref: "#/components/parameters/ChannelId" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/ChannelUpdate" } example: { name: Suporte } responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Channel" } "400": { $ref: "#/components/responses/BadRequest" } "404": { $ref: "#/components/responses/NotFound" } delete: tags: [Canais] operationId: deleteChannel summary: Remover canal de WhatsApp description: | Desconecta e remove o WhatsApp da organização. As conversas continuam no painel. O Chat do site só pode ser removido no painel. parameters: - $ref: "#/components/parameters/ChannelId" responses: "200": description: Removido. content: application/json: schema: { $ref: "#/components/schemas/Deleted" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } /channels/{channel_id}/connect: post: tags: [Canais] operationId: connectChannel summary: Gerar QR Code (ou código de pareamento) description: | Inicia o pareamento de um WhatsApp que ainda não está conectado e devolve o QR Code. Para conectar sem câmera, envie `phone`: a resposta traz um `pairing_code` de 8 caracteres para digitar no celular (WhatsApp → Aparelhos conectados → Conectar com número de telefone). O QR Code vale por cerca de 20 a 40 segundos e é renovado automaticamente: consulte `GET /channels/{channel_id}/qr` a cada poucos segundos até o `status` virar `connected` (ou assine o webhook `channel.status_updated`). parameters: - $ref: "#/components/parameters/ChannelId" requestBody: required: false content: application/json: schema: { $ref: "#/components/schemas/ChannelConnect" } examples: qr: summary: QR Code value: {} pareamento: summary: Código de pareamento value: { phone: "5511999998888" } responses: "200": description: QR Code (ou código) gerado. content: application/json: schema: { $ref: "#/components/schemas/ChannelConnection" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } "502": { $ref: "#/components/responses/ProviderError" } /channels/{channel_id}/qr: get: tags: [Canais] operationId: getChannelQr summary: QR Code atual description: | O QR Code mais recente do pareamento em andamento, para mostrar na sua tela. Não gera um novo pareamento (para isso use `POST /channels/{channel_id}/connect`). Quando o celular lê o código, `status` vira `connected` e `qr_code` volta `null`. parameters: - $ref: "#/components/parameters/ChannelId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/ChannelConnection" } "400": { $ref: "#/components/responses/BadRequest" } "404": { $ref: "#/components/responses/NotFound" } /channels/{channel_id}/disconnect: post: tags: [Canais] operationId: disconnectChannel summary: Desconectar WhatsApp description: | Encerra a sessão do WhatsApp neste canal (o mesmo que "Desconectar" no painel). As conversas continuam no painel; para usar o número de novo, chame `POST /channels/{channel_id}/connect`. parameters: - $ref: "#/components/parameters/ChannelId" responses: "200": description: Desconectado. content: application/json: schema: { $ref: "#/components/schemas/Channel" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } /messages/{message_id}/reaction: post: tags: [Mensagens] operationId: reactToMessage summary: Reagir a uma mensagem description: Envia (ou remove, com `emoji` vazio) uma reação no WhatsApp. parameters: - $ref: "#/components/parameters/MessageId" requestBody: required: true content: application/json: schema: type: object required: [emoji] properties: emoji: { type: string, description: 'Um emoji (ex.: "👍"). String vazia remove a reação.' } example: { emoji: "👍" } responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Message" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } "422": { $ref: "#/components/responses/ChannelUnavailable" } "502": { $ref: "#/components/responses/ProviderError" } /contacts/{contact_id}/read: post: tags: [Contatos] operationId: markContactRead summary: Marcar conversa como lida description: Zera o contador de não lidas da conversa no painel. parameters: - $ref: "#/components/parameters/ContactId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Contact" } "404": { $ref: "#/components/responses/NotFound" } /groups: get: tags: [Grupos] operationId: listGroups summary: Listar grupos description: | Grupos do WhatsApp que aparecem no painel, dos mais recentes para os mais antigos. Um grupo também é uma conversa: use o `id` dele como `contact_id` para ler o histórico (`GET /contacts/{contact_id}/messages`) ou enviar mensagem (`POST /messages`). parameters: - name: search in: query description: Parte do nome do grupo. schema: { type: string, maxLength: 100 } - name: channel_id in: query schema: { type: string, format: uuid } - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Cursor" responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/Group" } /groups/{group_id}/participants: get: tags: [Grupos] operationId: listGroupParticipants summary: Participantes do grupo description: Busca na hora, no WhatsApp conectado, quem está no grupo. Precisa do canal conectado. parameters: - name: group_id in: path required: true schema: { type: string, format: uuid } responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/GroupParticipant" } "404": { $ref: "#/components/responses/NotFound" } "422": { $ref: "#/components/responses/ChannelUnavailable" } "502": { $ref: "#/components/responses/ProviderError" } /members: get: tags: [Equipe] operationId: listMembers summary: Listar membros da equipe description: Use o `id` do membro em `assigned_to` para atribuir uma conversa. responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/Member" } /members/{member_id}: get: tags: [Equipe] operationId: getMember summary: Buscar membro parameters: - name: member_id in: path required: true schema: { type: string, format: uuid } responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Member" } "404": { $ref: "#/components/responses/NotFound" } /ai-agents: get: tags: [Agentes de IA] operationId: listAiAgents summary: Listar agentes de IA responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/AiAgent" } /ai-agents/{agent_id}: get: tags: [Agentes de IA] operationId: getAiAgent summary: Buscar agente de IA parameters: - $ref: "#/components/parameters/AgentId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/AiAgent" } "404": { $ref: "#/components/responses/NotFound" } patch: tags: [Agentes de IA] operationId: updateAiAgent summary: Ligar ou desligar agente de IA description: | `active: false` faz o agente parar de responder. Desligar o agente **padrão** desliga a IA da organização inteira (o mesmo interruptor do painel). parameters: - $ref: "#/components/parameters/AgentId" requestBody: required: true content: application/json: schema: type: object required: [active] properties: active: { type: boolean } example: { active: false } responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/AiAgent" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } /templates: get: tags: [Campanhas] operationId: listTemplates summary: Listar templates description: | Templates de mensagem do WhatsApp Oficial (API da Meta) da organização. Só templates com `ready: true` (aprovados pela TopTalking e pela Meta) podem ser usados em campanhas. Templates são criados e enviados para aprovação no painel, em Campanhas → Templates. parameters: - name: ready in: query description: "`true` só os prontos para uso." schema: { type: boolean } responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/Template" } /campaigns: get: tags: [Campanhas] operationId: listCampaigns summary: Listar campanhas description: Das mais recentes para as mais antigas. parameters: - name: status in: query schema: { $ref: "#/components/schemas/CampaignStatus" } - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Cursor" responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/Campaign" } post: tags: [Campanhas] operationId: createCampaign summary: Criar campanha description: | Cria um disparo pelo WhatsApp Oficial com um template pronto (`ready: true`). A campanha nasce `pending_approval`: a equipe da TopTalking revisa e, ao aprovar, o envio começa sozinho (`approved` → `running` → `completed`). O preço por mensagem é congelado na criação; consulte antes com `POST /campaigns/quote`. `variables` de cada destinatário preenche, na ordem, as variáveis `{{1}}`, `{{2}}`… do corpo do template. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/CampaignCreate" } example: name: Black Friday 2026 template: { name: promo_black_friday, language: pt_BR } recipients: - { phone: "5511999998888", name: Maria, variables: [Maria, "30%"] } - { phone: "5521988887777", name: João, variables: [João, "30%"] } skip_already_sent: false responses: "201": description: Criada e enviada para aprovação. content: application/json: schema: { $ref: "#/components/schemas/Campaign" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } "422": { $ref: "#/components/responses/ChannelUnavailable" } /campaigns/quote: post: tags: [Campanhas] operationId: quoteCampaign summary: Simular preço da campanha description: Quanto a campanha vai custar, pela tabela de preço da sua organização. Não cria nada. requestBody: required: true content: application/json: schema: type: object required: [template, recipients_count] properties: template: { $ref: "#/components/schemas/TemplateRef" } recipients_count: { type: integer, minimum: 1, maximum: 100000 } example: template: { name: promo_black_friday, language: pt_BR } recipients_count: 1500 responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/CampaignQuote" } "400": { $ref: "#/components/responses/BadRequest" } "404": { $ref: "#/components/responses/NotFound" } /campaigns/{campaign_id}: get: tags: [Campanhas] operationId: getCampaign summary: Buscar campanha parameters: - $ref: "#/components/parameters/CampaignId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Campaign" } "404": { $ref: "#/components/responses/NotFound" } /campaigns/{campaign_id}/recipients: get: tags: [Campanhas] operationId: listCampaignRecipients summary: Destinatários da campanha description: Status de envio de cada destinatário, na ordem em que foram enviados à API. parameters: - $ref: "#/components/parameters/CampaignId" - name: status in: query schema: { type: string, enum: [pending, sending, sent, delivered, read, failed] } - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Cursor" responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/CampaignRecipient" } "404": { $ref: "#/components/responses/NotFound" } /campaigns/{campaign_id}/pause: post: tags: [Campanhas] operationId: pauseCampaign summary: Pausar campanha description: Interrompe o envio de uma campanha `approved` ou `running`. Quem já recebeu não é afetado. parameters: - $ref: "#/components/parameters/CampaignId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Campaign" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } /campaigns/{campaign_id}/resume: post: tags: [Campanhas] operationId: resumeCampaign summary: Retomar campanha description: Volta a enviar uma campanha `paused` (continua de onde parou). parameters: - $ref: "#/components/parameters/CampaignId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Campaign" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } /contacts: get: tags: [Contatos] operationId: listContacts summary: Listar contatos description: Ordenados pela última mensagem (mais recentes primeiro). parameters: - name: search in: query description: Busca por nome ou telefone (parcial). schema: { type: string, maxLength: 100 } - name: phone in: query description: Telefone exato (aceita formatado; é normalizado para dígitos). schema: { type: string, example: "5511999998888" } - name: stage_id in: query description: Só contatos nesta etapa do Kanban. schema: { type: string, format: uuid } - name: channel_id in: query schema: { type: string, format: uuid } - name: archived in: query description: "`true` só arquivados, `false` só ativos. Sem o parâmetro, todos." schema: { type: boolean } - name: is_group in: query description: "`false` só conversas individuais, `true` só grupos. Sem o parâmetro, todos." schema: { type: boolean } - name: assigned_to in: query description: "ID do membro responsável, ou `none` para conversas sem responsável." schema: { type: string } - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Cursor" responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/Contact" } "401": { $ref: "#/components/responses/Unauthorized" } post: tags: [Contatos] operationId: upsertContact summary: Criar ou atualizar contato description: | Cria o contato pelo telefone. Se já existir um contato com esse telefone na organização, atualiza os campos enviados e devolve `200` em vez de `201`. Como há um contato por número **e canal**, quando o telefone existe em mais de um canal é atualizada a conversa do `channel_id` informado ou, sem ele, a que teve mensagem mais recente. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/ContactCreate" } example: phone: "+55 11 99999-8888" name: Maria Souza email: maria@exemplo.com responses: "200": description: Contato já existia e foi atualizado. content: application/json: schema: { $ref: "#/components/schemas/Contact" } "201": description: Contato criado. content: application/json: schema: { $ref: "#/components/schemas/Contact" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } /contacts/{contact_id}: get: tags: [Contatos] operationId: getContact summary: Buscar contato parameters: - $ref: "#/components/parameters/ContactId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Contact" } "404": { $ref: "#/components/responses/NotFound" } patch: tags: [Contatos, Kanban] operationId: updateContact summary: Atualizar contato (e mover no Kanban) description: | Envie só os campos que quer mudar. Para mover o card no Kanban, envie `stage_id` (ou `null` para tirar de qualquer etapa). Mover para a etapa final (`is_final: true`) também finaliza a conversa, igual ao painel. parameters: - $ref: "#/components/parameters/ContactId" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/ContactUpdate" } example: stage_id: 9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11 responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Contact" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } /contacts/{contact_id}/messages: get: tags: [Contatos, Mensagens] operationId: listContactMessages summary: Histórico da conversa description: Mensagens do contato, das mais recentes para as mais antigas. parameters: - $ref: "#/components/parameters/ContactId" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Cursor" responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/Message" } "404": { $ref: "#/components/responses/NotFound" } /messages: post: tags: [Mensagens] operationId: sendMessage summary: Enviar mensagem description: | Envia texto e/ou mídia pelo WhatsApp (ou responde no Chat do site). Informe o destino com `to` (telefone) **ou** `contact_id`. Se o telefone ainda não for contato, o contato é criado e a conversa aparece no painel. O canal usado é, nesta ordem: `channel_id` (se enviado), o canal da conversa (se conectado) ou o WhatsApp principal conectado. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/MessageCreate" } examples: texto: summary: Texto value: to: "5511999998888" text: "Olá, Maria! Seu pedido #1234 saiu para entrega." midia: summary: Documento com legenda value: contact_id: 3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60 text: Segue o boleto. media: url: https://exemplo.com/boleto-1234.pdf type: document filename: boleto-1234.pdf responses: "201": description: Mensagem enviada (status `sent`). content: application/json: schema: { $ref: "#/components/schemas/Message" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } "422": { $ref: "#/components/responses/ChannelUnavailable" } "502": { $ref: "#/components/responses/ProviderError" } /messages/{message_id}: get: tags: [Mensagens] operationId: getMessage summary: Buscar mensagem description: Use para acompanhar o status de entrega (`sent` → `delivered` → `read`). parameters: - $ref: "#/components/parameters/MessageId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/Message" } "404": { $ref: "#/components/responses/NotFound" } patch: tags: [Mensagens] operationId: editMessage summary: Editar mensagem enviada description: Edita o texto de uma mensagem de texto **enviada** (outbound) em até 15 minutos. parameters: - $ref: "#/components/parameters/MessageId" requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: { type: string, maxLength: 4096 } example: { text: "Seu pedido #1234 sai amanhã cedo." } responses: "200": description: Editada (`edited_at` preenchido). content: application/json: schema: { $ref: "#/components/schemas/Message" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } "422": { $ref: "#/components/responses/ChannelUnavailable" } "502": { $ref: "#/components/responses/ProviderError" } delete: tags: [Mensagens] operationId: deleteMessage summary: Apagar mensagem description: | `scope=everyone` (padrão) apaga para todos no WhatsApp: só mensagens enviadas, em até 48 horas; o texto vira "Mensagem apagada". `scope=me` só esconde a mensagem do painel e do histórico (`GET /contacts/{contact_id}/messages`); ela continua acessível por `GET /messages/{message_id}`. parameters: - $ref: "#/components/parameters/MessageId" - name: scope in: query schema: { type: string, enum: [everyone, me], default: everyone } responses: "200": description: Apagada. content: application/json: schema: { $ref: "#/components/schemas/Message" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } "422": { $ref: "#/components/responses/ChannelUnavailable" } "502": { $ref: "#/components/responses/ProviderError" } /stages: get: tags: [Kanban] operationId: listStages summary: Listar etapas do Kanban description: "Na ordem das colunas. A etapa com `is_final: true` finaliza a conversa." responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/Stage" } /scheduled-messages: get: tags: [Mensagens agendadas] operationId: listScheduledMessages summary: Listar mensagens agendadas description: Ordenadas pelo horário de envio (mais próximas primeiro). parameters: - name: status in: query schema: { $ref: "#/components/schemas/ScheduledMessageStatus" } - name: contact_id in: query schema: { type: string, format: uuid } - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Cursor" responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/ScheduledMessage" } post: tags: [Mensagens agendadas] operationId: createScheduledMessage summary: Agendar mensagem description: | A mensagem aparece na Agenda do painel e é enviada no horário (verificação a cada minuto). O texto aceita as variáveis `{{nome}}` e `{{telefone}}`. Não disponível para conversas do Chat do site nem para grupos. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/ScheduledMessageCreate" } example: to: "5511999998888" send_at: "2026-12-01T12:00:00-03:00" text: "Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h." responses: "201": description: Agendada. content: application/json: schema: { $ref: "#/components/schemas/ScheduledMessage" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } /scheduled-messages/{scheduled_message_id}: get: tags: [Mensagens agendadas] operationId: getScheduledMessage summary: Buscar mensagem agendada parameters: - $ref: "#/components/parameters/ScheduledMessageId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/ScheduledMessage" } "404": { $ref: "#/components/responses/NotFound" } delete: tags: [Mensagens agendadas] operationId: cancelScheduledMessage summary: Cancelar mensagem agendada description: Só cancela o que ainda está `pending`. Devolve o item com status `canceled`. parameters: - $ref: "#/components/parameters/ScheduledMessageId" responses: "200": description: Cancelada. content: application/json: schema: { $ref: "#/components/schemas/ScheduledMessage" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } /webhooks: get: tags: [Webhooks] operationId: listWebhooks summary: Listar webhooks responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/WebhookEndpoint" } post: tags: [Webhooks] operationId: createWebhook summary: Criar webhook description: | A resposta traz o `secret` (só desta vez) para você validar a assinatura dos eventos. Máximo de 10 webhooks por organização. parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/WebhookCreate" } example: url: https://meusistema.com.br/webhooks/toptalking events: [message.received, contact.stage_changed] description: CRM interno responses: "201": description: Criado. content: application/json: schema: { $ref: "#/components/schemas/WebhookEndpointWithSecret" } "400": { $ref: "#/components/responses/BadRequest" } "403": { $ref: "#/components/responses/Forbidden" } "409": { $ref: "#/components/responses/Conflict" } "422": description: "Limite de 10 webhooks por organização (`limit_reached`)." content: application/json: schema: { $ref: "#/components/schemas/Error" } /webhooks/{webhook_id}: get: tags: [Webhooks] operationId: getWebhook summary: Buscar webhook parameters: - $ref: "#/components/parameters/WebhookId" responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/WebhookEndpoint" } "404": { $ref: "#/components/responses/NotFound" } patch: tags: [Webhooks] operationId: updateWebhook summary: Atualizar webhook parameters: - $ref: "#/components/parameters/WebhookId" requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/WebhookUpdate" } responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/WebhookEndpoint" } "400": { $ref: "#/components/responses/BadRequest" } "404": { $ref: "#/components/responses/NotFound" } delete: tags: [Webhooks] operationId: deleteWebhook summary: Excluir webhook parameters: - $ref: "#/components/parameters/WebhookId" responses: "200": description: Excluído. content: application/json: schema: { $ref: "#/components/schemas/Deleted" } "404": { $ref: "#/components/responses/NotFound" } /webhooks/{webhook_id}/test: post: tags: [Webhooks] operationId: testWebhook summary: Enviar evento de teste description: Envia um evento `ping` para o endpoint (mesmo se estiver desativado). parameters: - $ref: "#/components/parameters/WebhookId" responses: "202": description: Na fila de entrega. content: application/json: schema: { $ref: "#/components/schemas/WebhookDelivery" } "404": { $ref: "#/components/responses/NotFound" } /webhooks/{webhook_id}/deliveries: get: tags: [Webhooks] operationId: listWebhookDeliveries summary: Histórico de entregas description: Últimas tentativas de entrega deste webhook (eventos ficam guardados por 30 dias). parameters: - $ref: "#/components/parameters/WebhookId" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Cursor" responses: "200": description: OK content: application/json: schema: allOf: - $ref: "#/components/schemas/ListEnvelope" - type: object properties: data: type: array items: { $ref: "#/components/schemas/WebhookDelivery" } "404": { $ref: "#/components/responses/NotFound" } webhooks: message.received: post: summary: Mensagem recebida description: Um contato mandou mensagem (WhatsApp, grupo ou Chat do site). requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: message.received } data: { $ref: "#/components/schemas/MessageEventData" } responses: "200": { description: Recebido. Qualquer 2xx confirma a entrega. } message.sent: post: summary: Mensagem enviada description: | Uma mensagem saiu pela conta — enviada pela equipe, pela IA, por campanha, pela Agenda ou pela API. Pode chegar com status `queued`; acompanhe em `message.status_updated`. requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: message.sent } data: { $ref: "#/components/schemas/MessageEventData" } responses: "200": { description: Recebido. } message.status_updated: post: summary: Status de entrega mudou requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: message.status_updated } data: allOf: - $ref: "#/components/schemas/MessageEventData" - type: object properties: previous_status: { $ref: "#/components/schemas/MessageStatus" } responses: "200": { description: Recebido. } contact.created: post: summary: Contato criado requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: contact.created } data: type: object properties: contact: { $ref: "#/components/schemas/Contact" } responses: "200": { description: Recebido. } contact.updated: post: summary: Contato alterado description: | Disparado quando muda nome, e-mail, etapa, responsável, IA ligada/desligada, arquivamento ou finalização. `changed_fields` diz o que mudou e `previous` traz os valores anteriores desses campos. requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: contact.updated } data: type: object properties: contact: { $ref: "#/components/schemas/Contact" } changed_fields: type: array items: type: string enum: [name, email, stage, assigned_to, ai_enabled, archived_at, resolved_at] previous: type: object description: Valores anteriores, só dos campos alterados. additionalProperties: true responses: "200": { description: Recebido. } contact.stage_changed: post: summary: Card mudou de etapa no Kanban requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: contact.stage_changed } data: type: object properties: contact: { $ref: "#/components/schemas/Contact" } stage: oneOf: - $ref: "#/components/schemas/StageRef" - type: "null" previous_stage: oneOf: - $ref: "#/components/schemas/StageRef" - type: "null" responses: "200": { description: Recebido. } scheduled_message.sent: post: summary: Mensagem agendada enviada requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: scheduled_message.sent } data: type: object properties: scheduled_message: { $ref: "#/components/schemas/ScheduledMessage" } responses: "200": { description: Recebido. } scheduled_message.failed: post: summary: Mensagem agendada falhou requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: scheduled_message.failed } data: type: object properties: scheduled_message: { $ref: "#/components/schemas/ScheduledMessage" } responses: "200": { description: Recebido. } channel.status_updated: post: summary: Conexão do WhatsApp mudou description: | O canal conectou (o QR Code foi lido), caiu ou foi desconectado. Use para saber o momento exato em que o pareamento terminou. requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: channel.status_updated } data: type: object properties: channel: { $ref: "#/components/schemas/Channel" } previous_status: { type: [string, "null"] } responses: "200": { description: Recebido. } campaign.status_updated: post: summary: Status da campanha mudou description: Aprovada, pedido de ajustes, começou a enviar, pausada, concluída ou falhou. requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: campaign.status_updated } data: type: object properties: campaign: { $ref: "#/components/schemas/Campaign" } previous_status: { $ref: "#/components/schemas/CampaignStatus" } responses: "200": { description: Recebido. } ping: post: summary: Evento de teste description: Enviado pelo botão "Enviar teste" do painel ou por `POST /webhooks/{id}/test`. requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/EventEnvelope" - type: object properties: type: { const: ping } data: type: object properties: webhook_id: { type: string, format: uuid } message: { type: string, example: Evento de teste enviado pela TopTalking. } responses: "200": { description: Recebido. } components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: tt_live_... description: "Chave de API gerada em Configurações → Desenvolvedor. Também aceita o cabeçalho `X-Api-Key`." parameters: Limit: name: limit in: query description: Itens por página (1 a 100). schema: { type: integer, minimum: 1, maximum: 100, default: 20 } Cursor: name: cursor in: query description: Valor de `next_cursor` da página anterior. schema: { type: string } IdempotencyKey: name: Idempotency-Key in: header description: | Opcional. Repetir a mesma requisição com a mesma chave (até 24 h) devolve a resposta original em vez de executar de novo — use para retentativas seguras. schema: { type: string, maxLength: 255 } ChannelId: name: channel_id in: path required: true schema: { type: string, format: uuid } ContactId: name: contact_id in: path required: true schema: { type: string, format: uuid } ScheduledMessageId: name: scheduled_message_id in: path required: true schema: { type: string, format: uuid } WebhookId: name: webhook_id in: path required: true schema: { type: string, format: uuid } MessageId: name: message_id in: path required: true schema: { type: string, format: uuid } AgentId: name: agent_id in: path required: true schema: { type: string, format: uuid } CampaignId: name: campaign_id in: path required: true schema: { type: string, format: uuid } headers: X-Request-Id: description: "Identificador da requisição (ex.: req_6f1d2c3b4a5e). Cite ao pedir suporte." schema: { type: string } X-RateLimit-Limit: description: Requisições permitidas por minuto para esta chave. schema: { type: integer, example: 120 } X-RateLimit-Remaining: schema: { type: integer } X-RateLimit-Reset: description: Unix timestamp (segundos) de quando a janela reinicia. schema: { type: integer } responses: BadRequest: description: Requisição inválida. content: application/json: schema: { $ref: "#/components/schemas/Error" } example: error: type: invalid_request_error code: missing_field message: Informe `to` ou `contact_id`. param: to request_id: req_6f1d2c3b4a5e Unauthorized: description: Chave ausente, inválida, revogada ou expirada. content: application/json: schema: { $ref: "#/components/schemas/Error" } example: error: type: authentication_error code: invalid_api_key message: Chave de API inválida. request_id: req_6f1d2c3b4a5e Forbidden: description: A chave é somente leitura. content: application/json: schema: { $ref: "#/components/schemas/Error" } example: error: type: permission_error code: read_only_key message: Esta chave é somente leitura. request_id: req_6f1d2c3b4a5e NotFound: description: Não encontrado (ou de outra organização). content: application/json: schema: { $ref: "#/components/schemas/Error" } Conflict: description: Conflito de estado ou Idempotency-Key reutilizada com outro corpo. content: application/json: schema: { $ref: "#/components/schemas/Error" } ChannelUnavailable: description: | Não há canal conectado para a operação: WhatsApp desconectado ou, em campanhas, nenhum número do WhatsApp Oficial conectado. content: application/json: schema: { $ref: "#/components/schemas/Error" } example: error: type: channel_error code: channel_unavailable message: WhatsApp desconectado. Reconecte no painel. request_id: req_6f1d2c3b4a5e ProviderError: description: | O WhatsApp recusou a operação ou não respondeu (envio, reação, edição, pareamento, participantes). Num envio, a mensagem fica registrada com status `failed` e o erro traz `message_id`. content: application/json: schema: { $ref: "#/components/schemas/Error" } RateLimited: description: Limite de requisições por minuto excedido. Veja o cabeçalho `Retry-After`. headers: Retry-After: schema: { type: integer } content: application/json: schema: { $ref: "#/components/schemas/Error" } schemas: Error: type: object required: [error] properties: error: type: object required: [type, code, message, request_id] properties: type: type: string enum: - invalid_request_error - authentication_error - permission_error - not_found_error - conflict_error - channel_error - rate_limit_error - api_error code: type: string description: | Código estável para tratar no seu código (status HTTP → type → code): - 400 invalid_request_error: invalid_json, missing_field, invalid_field, invalid_phone - 401 authentication_error: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key - 403 permission_error: read_only_key - 404 not_found_error: resource_not_found, route_not_found - 405 invalid_request_error: method_not_allowed - 409 conflict_error: idempotency_conflict, invalid_state - 422 invalid_request_error: limit_reached - 422 channel_error: channel_unavailable - 429 rate_limit_error: rate_limit_exceeded - 500 api_error: internal_error - 502 channel_error: provider_error (traz também `message_id` da mensagem gravada como failed) - 502 api_error: internal_error (falha passageira) - 503 api_error: service_unavailable (API temporariamente indisponível) - 504 api_error: timeout (a requisição demorou demais) message: { type: string, description: Mensagem em português para humanos. } param: { type: string, description: Campo que causou o erro, quando houver. } request_id: { type: string } ListEnvelope: type: object required: [object, data, has_more, next_cursor] properties: object: { const: list } data: { type: array, items: {} } has_more: { type: boolean } next_cursor: type: [string, "null"] description: Passe em `cursor` para buscar a próxima página. Deleted: type: object properties: id: { type: string, format: uuid } object: { type: string, example: channel, description: "Tipo do objeto removido (ex.: channel, webhook_endpoint)." } deleted: { const: true } Me: type: object properties: object: { const: account } organization: type: object properties: id: { type: string, format: uuid } name: { type: string } api_key: type: object properties: id: { type: string, format: uuid } name: { type: string } prefix: { type: string, example: tt_live_a1b2c3d4 } access: { type: string, enum: [full, read] } created_at: { type: string, format: date-time } rate_limit: type: object properties: limit: { type: integer, example: 120 } remaining: { type: integer } reset_at: { type: string, format: date-time } Channel: type: object properties: id: { type: string, format: uuid } object: { const: channel } type: type: string enum: [whatsapp, webchat] description: "`whatsapp` = número conectado; `webchat` = Chat do site." name: { type: string, example: Vendas } phone: { type: [string, "null"], example: "5511999998888" } status: type: string enum: [connected, connecting, disconnected] description: "`connected` = pronto para enviar; `connecting` = aguardando a leitura do QR Code ou reconectando; `disconnected` = desconectado." example: connected is_primary: { type: boolean } created_at: { type: string, format: date-time } ChannelCreate: type: object properties: name: { type: string, maxLength: 80, description: "Nome do canal no painel (ex.: \"Vendas\")." } ChannelUpdate: type: object required: [name] properties: name: { type: string, maxLength: 80 } ChannelConnect: type: object properties: phone: type: string description: Opcional. Com o número (DDI + DDD), a resposta traz `pairing_code` em vez do QR Code. example: "5511999998888" ChannelConnection: type: object description: Estado do pareamento de um canal de WhatsApp. properties: object: { const: channel_connection } channel: { $ref: "#/components/schemas/Channel" } status: type: string enum: [connected, connecting, disconnected] description: "`connected` = o celular leu o código e o canal já envia mensagens." qr_code: type: [string, "null"] description: Imagem PNG em data URL (`data:image/png;base64,...`), pronta para ``. `null` se conectado ou sem pareamento em andamento. qr_code_generated_at: { type: [string, "null"], format: date-time } pairing_code: type: [string, "null"] description: "Código de 8 caracteres, exibido com hífen no meio (só quando `phone` foi enviado em `/connect`)." example: "WZYE-H1YY" Group: type: object properties: id: { type: string, format: uuid, description: "É o `contact_id` do grupo: use para ler o histórico e enviar mensagens." } object: { const: group } name: { type: [string, "null"] } jid: { type: [string, "null"], example: "120363012345678901@g.us" } channel_id: { type: [string, "null"], format: uuid } unread_count: { type: integer } last_message_at: { type: [string, "null"], format: date-time } last_message_preview: { type: [string, "null"] } archived_at: { type: [string, "null"], format: date-time } created_at: { type: string, format: date-time } GroupParticipant: type: object properties: object: { const: group_participant } phone: { type: [string, "null"], example: "5511999998888" } name: { type: [string, "null"] } role: { type: string, enum: [member, admin, superadmin], description: "`superadmin` = quem criou o grupo." } avatar_url: { type: [string, "null"] } Member: type: object properties: id: { type: string, format: uuid, description: "ID do usuário. Use em `assigned_to`." } object: { const: member } name: { type: [string, "null"] } email: { type: [string, "null"] } role: { type: string, enum: [owner, admin, agent] } avatar_url: { type: [string, "null"] } joined_at: { type: string, format: date-time } AiAgent: type: object properties: id: { type: string, format: uuid } object: { const: ai_agent } name: { type: string } description: { type: [string, "null"] } active: { type: boolean, description: "Se o agente está respondendo." } is_default: { type: boolean, description: "Agente usado nas conversas sem agente definido." } tone: { type: [string, "null"], example: profissional_caloroso } strict_mode: { type: boolean, description: "Só responde com base nas FAQs, na base de conhecimento e nas instruções." } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } TemplateRef: type: object required: [name] properties: name: { type: string, example: promo_black_friday } language: { type: string, default: pt_BR } Template: type: object properties: id: { type: string, format: uuid } object: { const: template } name: { type: string, example: promo_black_friday } language: { type: string, example: pt_BR } category: { type: [string, "null"], enum: [MARKETING, UTILITY, AUTHENTICATION, null] } status: type: [string, "null"] description: Status na Meta (APPROVED, PENDING, REJECTED, PAUSED, DISABLED). review_status: { type: string, description: "Revisão interna da TopTalking: approved, pending ou rejected." } ready: { type: boolean, description: "`true` = pode ser usado em campanhas." } body: { type: [string, "null"], description: "Texto do corpo com as variáveis `{{1}}`, `{{2}}`…" } variables_count: { type: integer, description: "Tamanho de `variables` em cada destinatário: as variáveis do corpo mais 1 por botão de link com variável (ex.: `https://site.com/checkout/{{1}}`)." } header_type: { type: [string, "null"], enum: [TEXT, IMAGE, VIDEO, DOCUMENT, null] } rejected_reason: { type: [string, "null"] } created_at: { type: string, format: date-time } CampaignStatus: type: string enum: [draft, pending_approval, approved, changes_requested, running, paused, completed, failed] description: | `draft` = rascunho criado no painel (campanhas da API já nascem `pending_approval`); `pending_approval` = aguardando a revisão da TopTalking; `changes_requested` = a revisão pediu ajustes (veja `review_notes`); `approved` = aprovada, começa a enviar em instantes; `running` = enviando; `paused`; `completed`; `failed`. Campaign: type: object properties: id: { type: string, format: uuid } object: { const: campaign } name: { type: string } status: { $ref: "#/components/schemas/CampaignStatus" } review_notes: { type: [string, "null"], description: "Observação da revisão (quando houver)." } template: type: object properties: name: { type: string } language: { type: string } category: { type: [string, "null"] } totals: type: object description: | Acumulados: `sent` inclui as entregues e lidas; `delivered` inclui as lidas. `recipients` = `pending` + `sent` + `failed`. properties: recipients: { type: integer } pending: { type: integer } sent: { type: integer } delivered: { type: integer } read: { type: integer } failed: { type: integer } pricing: type: object properties: unit_price: { type: number, example: 0.35, description: "Preço por mensagem, congelado na criação." } currency: { type: string, example: BRL } estimated_total: { type: number, description: "unit_price × destinatários." } charged_total: { type: number, description: "unit_price × mensagens já enviadas." } created_at: { type: string, format: date-time } submitted_at: { type: [string, "null"], format: date-time } reviewed_at: { type: [string, "null"], format: date-time } started_at: { type: [string, "null"], format: date-time } completed_at: { type: [string, "null"], format: date-time } CampaignCreate: type: object description: | Corpo de até 1 MB. Telefones repetidos na mesma requisição recebem uma mensagem só. Valores numéricos em `variables` viram texto. required: [name, template, recipients] properties: name: { type: string, maxLength: 120 } template: { $ref: "#/components/schemas/TemplateRef" } recipients: type: array minItems: 1 maxItems: 5000 items: type: object required: [phone] properties: phone: { type: string, example: "5511999998888" } name: { type: string } variables: type: array items: { type: string } description: "Valores de `{{1}}`, `{{2}}`… do corpo, na ordem. Se o template tiver botão de link com variável, o valor dele vem no fim da lista (só a parte que entra no lugar de `{{1}}`, ex.: `86`)." skip_already_sent: type: boolean default: false description: "Pula quem já recebeu alguma campanha desta organização. Se todos já receberam: 400 `invalid_field` (param `recipients`)." CampaignQuote: type: object properties: object: { const: campaign_quote } category: { type: [string, "null"], example: MARKETING } recipients: { type: integer } unit_price: { type: number, example: 0.35 } currency: { type: string, example: BRL } total: { type: number, example: 525 } CampaignRecipient: type: object properties: id: { type: string, format: uuid } object: { const: campaign_recipient } phone: { type: string } name: { type: [string, "null"] } contact_id: { type: [string, "null"], format: uuid } variables: { type: array, items: { type: string } } status: { type: string, enum: [pending, sending, sent, delivered, read, failed] } error: { type: [string, "null"] } sent_at: { type: [string, "null"], format: date-time } delivered_at: { type: [string, "null"], format: date-time } read_at: { type: [string, "null"], format: date-time } StageRef: type: object properties: id: type: [string, "null"] format: uuid description: "`null` se a etapa foi apagada depois (o nome antigo continua em `name`)." name: { type: string, example: Negociação } is_final: { type: boolean } Stage: type: object properties: id: { type: string, format: uuid } object: { const: stage } name: { type: string, example: Negociação } color: { type: string, example: "#8b5cf6" } position: { type: integer, description: Ordem da coluna (0 = primeira). } is_final: type: boolean description: Etapa terminal ("Finalizado"). Mover um card para ela finaliza a conversa. Contact: type: object properties: id: { type: string, format: uuid } object: { const: contact } name: { type: [string, "null"], example: Maria Souza } phone: { type: [string, "null"], description: Só dígitos, com DDI., example: "5511999998888" } email: { type: [string, "null"], format: email } channel_id: { type: [string, "null"], format: uuid } channel_type: { type: string, enum: [whatsapp, webchat] } is_group: { type: boolean, description: "Grupo do WhatsApp (sem IA). Envie mensagem usando o id do grupo como `contact_id`." } stage: oneOf: - $ref: "#/components/schemas/StageRef" - type: "null" ai_enabled: { type: boolean, description: Se a IA responde esta conversa. } ai_agent_id: { type: [string, "null"], format: uuid, description: "Agente de IA da conversa (`null` = agente padrão)." } assigned_to: { type: [string, "null"], format: uuid, description: "Membro responsável (veja GET /members)." } pinned: { type: boolean, description: Conversa fixada no topo. } unread_count: { type: integer } last_message_at: { type: [string, "null"], format: date-time } last_message_preview: { type: [string, "null"] } resolved_at: { type: [string, "null"], format: date-time, description: Quando a conversa foi finalizada. } archived_at: { type: [string, "null"], format: date-time } source: { type: string, example: api, description: "Origem: whatsapp, webchat, api, agenda, lead..." } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } ContactCreate: type: object required: [phone] properties: phone: type: string description: Com DDI e DDD. Números brasileiros sem `55` recebem o `55`. example: "+55 11 99999-8888" name: { type: string, maxLength: 120 } email: { type: string, format: email } stage_id: { type: string, format: uuid, description: Já coloca o card nesta etapa. } channel_id: type: string format: uuid description: Canal de WhatsApp da conversa. Padrão = WhatsApp principal. ContactUpdate: type: object properties: name: { type: string, maxLength: 120 } email: { type: [string, "null"], format: email } stage_id: type: [string, "null"] format: uuid description: Move o card no Kanban. `null` tira de qualquer etapa. ai_enabled: { type: boolean, description: Liga ou desliga a IA nesta conversa. } ai_agent_id: type: [string, "null"] format: uuid description: Agente de IA desta conversa (veja GET /ai-agents). `null` volta para o agente padrão. assigned_to: type: [string, "null"] format: uuid description: Membro responsável pela conversa (veja GET /members). `null` libera a conversa. pinned: { type: boolean, description: Fixa a conversa no topo da caixa de entrada. } resolved: { type: boolean, description: "`true` finaliza a conversa; `false` reabre." } archived: { type: boolean } MessageStatus: type: string enum: [queued, sent, delivered, read, failed] Media: type: object properties: url: { type: string, format: uri } mime_type: { type: [string, "null"], example: application/pdf } filename: { type: [string, "null"], example: boleto-1234.pdf } Message: type: object properties: id: { type: string, format: uuid } object: { const: message } contact_id: { type: [string, "null"], format: uuid } channel_id: { type: [string, "null"], format: uuid } direction: { type: string, enum: [inbound, outbound], description: "`inbound` = o contato mandou." } type: type: string description: "Valores comuns: text, image, video, audio, document, sticker. Trate valores desconhecidos como mídia genérica." example: text text: type: [string, "null"] description: Texto ou legenda. Mídia sem legenda pode trazer um rótulo como "📷 Foto". media: oneOf: - $ref: "#/components/schemas/Media" - type: "null" status: { $ref: "#/components/schemas/MessageStatus" } error: { type: [string, "null"], description: Motivo da falha quando `status` = `failed`. } reply_to_id: { type: [string, "null"], format: uuid, description: Mensagem citada (resposta). } reaction: { type: [string, "null"], description: "Última reação nesta mensagem, nossa ou do contato (ex.: 👍)." } sender_name: { type: [string, "null"], description: Em grupos, quem mandou. } transcript: { type: [string, "null"], description: Transcrição de áudio, quando houver. } created_at: { type: string, format: date-time } edited_at: { type: [string, "null"], format: date-time } MediaInput: type: object required: [url, type] properties: url: { type: string, format: uri, description: URL pública HTTPS do arquivo. } type: { type: string, enum: [image, video, audio, document] } filename: { type: string, description: Nome exibido (documentos). Padrão = nome na URL. } mime_type: { type: string, description: Padrão = deduzido pela extensão. } voice_note: type: boolean default: false description: "Com `type: audio`, envia como nota de voz (sem legenda)." MessageCreate: type: object description: Informe `to` ou `contact_id`, e `text` e/ou `media`. properties: to: type: string description: Telefone com DDI e DDD. Números brasileiros sem `55` recebem o `55`. example: "5511999998888" contact_id: { type: string, format: uuid, description: "Conversa de destino (também aceita o `id` de um grupo, veja GET /groups)." } channel_id: { type: string, format: uuid } text: { type: string, maxLength: 4096, description: Aceita a formatação do WhatsApp (*negrito*, _itálico_). } media: { $ref: "#/components/schemas/MediaInput" } reply_to_id: { type: string, format: uuid, description: Responde (cita) esta mensagem da conversa. } contact_name: { type: string, description: Nome usado se o contato ainda não existir. } ScheduledMessageStatus: type: string enum: [pending, processing, sent, failed, canceled] ScheduledMessage: type: object properties: id: { type: string, format: uuid } object: { const: scheduled_message } contact_id: { type: [string, "null"], format: uuid } to: { type: [string, "null"], description: Telefone de destino. } title: { type: string } text: { type: [string, "null"] } media: type: array items: { $ref: "#/components/schemas/Media" } send_at: { type: string, format: date-time } status: { $ref: "#/components/schemas/ScheduledMessageStatus" } sent_at: { type: [string, "null"], format: date-time } message_id: { type: [string, "null"], format: uuid, description: Mensagem gerada no envio. } error: { type: [string, "null"] } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } ScheduledMessageCreate: type: object required: [send_at] description: Informe `to` ou `contact_id`, e `text` e/ou `media`. properties: to: { type: string, example: "5511999998888" } contact_id: { type: string, format: uuid } send_at: type: string format: date-time description: ISO 8601 com fuso. No futuro e em até 365 dias. text: { type: string, maxLength: 4096, description: "Aceita {{nome}} e {{telefone}}." } media: type: array maxItems: 10 items: { $ref: "#/components/schemas/MediaInput" } description: O texto vira legenda do primeiro anexo que aceita legenda. title: { type: string, maxLength: 120, description: Título na Agenda. Padrão = "Mensagem via API". } WebhookEventType: type: string enum: - "*" - message.received - message.sent - message.status_updated - contact.created - contact.updated - contact.stage_changed - scheduled_message.sent - scheduled_message.failed - channel.status_updated - campaign.status_updated WebhookEndpoint: type: object properties: id: { type: string, format: uuid } object: { const: webhook_endpoint } url: { type: string, format: uri } description: { type: [string, "null"] } events: type: array items: { $ref: "#/components/schemas/WebhookEventType" } enabled: { type: boolean } disabled_reason: { type: [string, "null"], description: Preenchido quando a TopTalking desativou por falhas seguidas. } last_delivery_at: { type: [string, "null"], format: date-time } last_delivery_status: { type: [integer, "null"], description: Status HTTP da última entrega. } created_at: { type: string, format: date-time } updated_at: { type: string, format: date-time } WebhookEndpointWithSecret: allOf: - $ref: "#/components/schemas/WebhookEndpoint" - type: object properties: secret: { type: string, example: whsec_3f9a..., description: Use para validar `X-TopTalking-Signature`. } WebhookCreate: type: object required: [url] properties: url: { type: string, format: uri, description: Precisa ser HTTPS. } events: type: array items: { $ref: "#/components/schemas/WebhookEventType" } default: ["*"] description: { type: string, maxLength: 200 } enabled: { type: boolean, default: true } WebhookUpdate: type: object properties: url: { type: string, format: uri } events: type: array items: { $ref: "#/components/schemas/WebhookEventType" } description: { type: [string, "null"], maxLength: 200 } enabled: { type: boolean } WebhookDelivery: type: object properties: id: { type: string, format: uuid } object: { const: webhook_delivery } webhook_id: { type: string, format: uuid } event_id: { type: string, format: uuid } event_type: { type: string } status: { type: string, enum: [pending, processing, succeeded, failed] } attempts: { type: integer } response_status: { type: [integer, "null"] } error: { type: [string, "null"] } duration_ms: { type: [integer, "null"] } next_attempt_at: { type: [string, "null"], format: date-time } delivered_at: { type: [string, "null"], format: date-time } created_at: { type: string, format: date-time } EventEnvelope: type: object description: | Corpo de todo evento de webhook. Cabeçalhos enviados: `X-TopTalking-Signature: t=,v1=` (HMAC-SHA256 de "." com o secret), `X-TopTalking-Event`, `X-TopTalking-Event-Id`, `X-TopTalking-Delivery-Id`. properties: id: { type: string, format: uuid, description: ID do evento (o mesmo em retentativas — use para deduplicar). } object: { const: event } type: { type: string } created_at: { type: string, format: date-time } organization_id: { type: string, format: uuid } data: { type: object } MessageEventData: type: object properties: message: { $ref: "#/components/schemas/Message" } contact: oneOf: - $ref: "#/components/schemas/Contact" - type: "null"