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"