API REST: autenticação, erros, limites, paginação, idempotência e a referência completa dos endpoints # Introdução > O que é a API da TopTalking, o que dá para construir com ela e os conceitos principais (organização, canal, contato, mensagem, etapa do Kanban e mensagem agendada). A **TopTalking** é uma plataforma de atendimento no WhatsApp com CRM (Kanban) e agentes de IA. A **API da TopTalking** deixa o seu sistema — ou a sua IA — fazer pelo código o que a equipe faz no painel: enviar mensagens, criar e atualizar contatos, ler o histórico de conversas, mover cards no Kanban, agendar mensagens e receber eventos em tempo real. ## O que dá para construir * **Avisos automáticos:** confirmação de pedido, boleto, código de rastreio, lembrete de consulta. * **Captura de leads:** quem preenche o formulário do seu site vira contato no painel, já na etapa certa do Kanban, e recebe uma mensagem de boas-vindas. * **Sincronização com o seu CRM ou ERP:** quando o card muda de etapa na TopTalking, o seu sistema fica sabendo (webhook) — e vice-versa. * **Painéis e relatórios próprios:** liste conversas, etapas e mensagens para montar o seu dashboard. * **Agentes de IA:** conecte o Claude, o Cursor ou o ChatGPT à sua conta para consultar conversas e enviar mensagens com supervisão. * **Automações sem código:** n8n, Make e Zapier conversam com a API por HTTP e recebem os webhooks. ## Quatro jeitos de integrar [API REST](/referencia/visao-geral/)Endpoints HTTP + JSON em https\://platform.toptalking.com.br/v1. [Webhooks](/webhooks/visao-geral/)A TopTalking avisa o seu sistema quando algo acontece (mensagem recebida, etapa alterada…). [Servidor MCP](/mcp/visao-geral/)Ferramentas prontas para Claude, Cursor, VS Code e Windsurf usarem a sua conta. [llms.txt e prompts](/ia/visao-geral/)A documentação em texto puro e prompts prontos para Lovable, Claude, ChatGPT e outros. ## Conceitos | Conceito | O que é | Objeto na API | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | | **Organização** | A empresa (conta) na TopTalking. Toda chave de API pertence a uma organização e só enxerga os dados dela. | `organization` (em [`GET /me`](/referencia/conta/)) | | **Canal** | Por onde as conversas acontecem: um **número de WhatsApp** conectado por QR Code (`whatsapp`) ou o **Chat do site** (`webchat`). Só canais com status `connected` enviam mensagens. Dá para [conectar um WhatsApp pela API](/referencia/canais/#conectar-um-whatsapp-por-qr-code). | [`channel`](/referencia/canais/) | | **Contato** | Uma **conversa** da caixa de entrada: um por número de telefone e canal. Guarda nome, telefone, e-mail, etapa do Kanban, responsável, agente de IA e se a IA está ligada. | [`contact`](/referencia/contatos/) | | **Grupo** | Um grupo do WhatsApp. Também é uma conversa: o `id` do grupo é um `contact_id`. | [`group`](/referencia/grupos/) | | **Mensagem** | Uma mensagem da conversa, recebida (`inbound`) ou enviada (`outbound`), com texto e/ou mídia e status de entrega. | [`message`](/referencia/mensagens/) | | **Etapa** | Uma coluna do Kanban (funil). Cada contato está em no máximo uma etapa. A etapa final (`is_final: true`, “Finalizado”) encerra a conversa. | [`stage`](/referencia/kanban/) | | **Mensagem agendada** | Uma mensagem programada para sair em data e hora definidas — a mesma da **Agenda** do painel. | [`scheduled_message`](/referencia/mensagens-agendadas/) | | **Membro** | Uma pessoa da equipe (dono, administrador ou atendente). Pode ser responsável por conversas. | [`member`](/referencia/equipe/) | | **Agente de IA** | Um assistente que responde as conversas sozinho. Um deles é o padrão da organização. | [`ai_agent`](/referencia/agentes-ia/) | | **Campanha** | Um disparo para muitos contatos pelo **WhatsApp Oficial** (API da Meta), com um template aprovado e revisão da TopTalking. | [`campaign`](/referencia/campanhas/) | | **Webhook** | Um endereço HTTPS do seu sistema que recebe eventos da conta. | [`webhook_endpoint`](/referencia/webhooks/) | | **Chave de API** | A credencial (`tt_live_...`) que o seu backend usa para chamar a API e o MCP. | `api_key` (em [`GET /me`](/referencia/conta/)) | Tudo o que a API faz aparece no painel como se a equipe tivesse feito: uma mensagem enviada pela API aparece no chat, um contato criado pela API aparece na caixa de entrada e um card movido pela API muda de coluna no Kanban. ## Convenções * **URL base:** `https://platform.toptalking.com.br/v1` * **Formato:** JSON em UTF-8, nos pedidos e nas respostas (`Content-Type: application/json`). * **Datas:** ISO 8601 com fuso horário (ex.: `2026-11-11T14:30:00Z` ou `2026-11-11T11:30:00-03:00`). * **IDs:** UUID (ex.: `3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60`). * **Tipo do objeto:** todo recurso tem o campo `object` (`contact`, `message`, `list`…), útil para saber o que você recebeu. * **Telefones:** envie com DDI e DDD. A formatação é ignorada (só os dígitos contam) e números brasileiros sem `55` recebem o `55`. As respostas trazem só dígitos, ex.: `5511999998888`. * **Rastreio:** toda resposta tem o cabeçalho `X-Request-Id`. Guarde-o nos seus logs — ele ajuda o suporte a encontrar a sua requisição. A chave fica no servidor A API foi feita para ser chamada **de um backend**. Ela não aceita chamadas do navegador (não tem CORS, de propósito), porque a chave exposta no front-end daria a qualquer pessoa acesso ao WhatsApp da sua empresa. Veja [Autenticação e chaves](/comecando/autenticacao/). ## Próximo passo Siga o [Início rápido](/comecando/inicio-rapido/) para gerar a chave e enviar a sua primeira mensagem. # Início rápido > Gere a chave de API, teste com GET /me e envie a sua primeira mensagem de WhatsApp pela API da TopTalking com curl, JavaScript ou Python. Em cinco minutos você vai gerar uma chave, confirmar que ela funciona e mandar uma mensagem de WhatsApp pela API. **Antes de começar:** você precisa ser **administrador** da organização na TopTalking e ter um **WhatsApp conectado** — no painel ([Configurações → Conexões](https://toptalking.com.br/configuracoes/conexao)) ou pela própria API (veja abaixo). ## 1. Gere a chave de API 1. Abra o painel em [Configurações → Desenvolvedor](https://toptalking.com.br/configuracoes/desenvolvedor). 2. No cartão **Chaves de API**, clique em **Criar chave**. 3. Dê um nome que diga quem vai usar a chave (ex.: “Integração do site”), escolha o acesso **Acesso total** e a validade. 4. Copie a chave (`tt_live_...`) e guarde em um lugar seguro. **Ela é mostrada uma única vez.** ## 2. Guarde a chave numa variável de ambiente Nunca cole a chave direto no código. Use uma variável de ambiente (no servidor, um “secret”): * macOS / Linux ```bash export TOPTALKING_API_KEY="tt_live_SUA_CHAVE" ``` * Windows (PowerShell) ```powershell $env:TOPTALKING_API_KEY = "tt_live_SUA_CHAVE" ``` ## 3. Teste a chave com `GET /me` Quer ver a resposta sem abrir o terminal? Em [Conta](/referencia/conta/), clique em **Testar**, cole a chave e envie: a resposta real da API aparece na própria página. Todos os endpoints da [referência](/referencia/visao-geral/#teste-direto-na-documenta%C3%A7%C3%A3o) têm esse botão. * curl ```bash curl https://platform.toptalking.com.br/v1/me \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript teste.mjs ```js // Node.js 18+ (rode com: node teste.mjs) const res = await fetch('https://platform.toptalking.com.br/v1/me', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); console.log(res.status, await res.json()); ``` * Python teste.py ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/me", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) print(res.status_code, res.json()) ``` Se a chave estiver certa, a resposta é `200` com os dados da organização e da chave: ```json { "object": "account", "organization": { "id": "e044a615-e879-44bb-8510-948b10b5e93c", "name": "Loja Aurora" }, "api_key": { "id": "508a23eb-5e24-4997-b928-3fce4f0f5593", "name": "Integração do site", "prefix": "tt_live_a1b2c3d4", "access": "full", "created_at": "2026-11-11T13:02:11Z" }, "rate_limit": { "limit": 120, "remaining": 119, "reset_at": "2026-11-11T14:31:00Z" } } ``` Recebeu `401`? Confira se o cabeçalho está exatamente como `Authorization: Bearer tt_live_...` (com a palavra `Bearer` e um espaço). Veja [Erros](/comecando/erros/). ## 4. Envie a sua primeira mensagem Use o **seu próprio número** para testar. Informe o telefone com DDI e DDD: * curl ```bash curl https://platform.toptalking.com.br/v1/messages \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: teste-primeira-mensagem-001" \ -d '{ "to": "5511999998888", "text": "Olá! Esta é a minha primeira mensagem pela API da TopTalking. 🚀" }' ``` * JavaScript enviar.mjs ```js const res = await fetch('https://platform.toptalking.com.br/v1/messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'teste-primeira-mensagem-001', }, body: JSON.stringify({ to: '5511999998888', text: 'Olá! Esta é a minha primeira mensagem pela API da TopTalking. 🚀', }), }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); console.log('Mensagem enviada:', body.id, body.status); ``` * Python enviar.py ```python import os import requests res = requests.post( "https://platform.toptalking.com.br/v1/messages", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": "teste-primeira-mensagem-001", }, json={ "to": "5511999998888", "text": "Olá! Esta é a minha primeira mensagem pela API da TopTalking. 🚀", }, timeout=30, ) body = res.json() if not res.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}") print("Mensagem enviada:", body["id"], body["status"]) ``` Resposta `201`: ```json { "id": "03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Olá! Esta é a minha primeira mensagem pela API da TopTalking. 🚀", "media": null, "status": "sent", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:30:12Z", "edited_at": null } ``` Pronto: a mensagem chegou no WhatsApp e já aparece no chat do painel. Se o número ainda não era contato, o contato foi criado e a conversa está na caixa de entrada. ## Próximos passos * Conecte mais números de WhatsApp pela API: [Canais e conexões](/referencia/canais/). * Receba as respostas dos clientes com [webhooks](/webhooks/visao-geral/). * Veja tudo o que dá para fazer na [Referência da API](/referencia/visao-geral/). * Vai integrar com Lovable, Claude, Cursor ou ChatGPT? Comece por [Integrar com IA](/ia/visao-geral/). * Antes de colocar em produção, leia [Boas práticas no WhatsApp](/comecando/boas-praticas-whatsapp/). # Autenticação e chaves > Como criar, usar, proteger, expirar e revogar chaves de API da TopTalking (tt_live_...), os níveis de acesso (total ou somente leitura) e por que a chave nunca pode ir para o navegador. Toda chamada à API e ao servidor MCP é autenticada com uma **chave de API** da sua organização. A chave começa com `tt_live_`. ## Como enviar a chave Use o cabeçalho `Authorization` com o esquema `Bearer`: ```http Authorization: Bearer tt_live_SUA_CHAVE ``` Se a ferramenta que você usa só permite um cabeçalho “API key” com nome livre, também aceitamos: ```http X-Api-Key: tt_live_SUA_CHAVE ``` ## Criar uma chave Só **administradores** da organização criam e revogam chaves. As chaves estão disponíveis em **todos os planos**. 1. No painel, abra [Configurações → Desenvolvedor](https://toptalking.com.br/configuracoes/desenvolvedor). 2. No cartão **Chaves de API**, clique em **Criar chave**. 3. Preencha: * **Nome:** quem ou o que vai usar a chave (ex.: “Integração do ERP”, “Claude do time de vendas”). Ajuda a saber o que revogar depois. * **Acesso:** **Acesso total** ou **Somente leitura** (veja abaixo). * **Validade:** sem validade, 30 dias, 90 dias ou 1 ano. Depois da validade a chave para de funcionar sozinha. 4. Copie a chave e guarde num cofre de senhas ou nos “secrets” do seu servidor. **A chave completa aparece uma única vez** — a TopTalking guarda só uma impressão digital (hash) dela. Se perder, revogue e crie outra. Você pode ter até **25 chaves ativas** por organização. Use uma chave por sistema, automação ou pessoa: assim dá para revogar uma sem derrubar as outras. ## Níveis de acesso | Acesso | Valor em `GET /me` | O que pode fazer | | ------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------- | | **Acesso total** | `full` | Tudo: ler, enviar mensagens, criar e alterar contatos, mover cards, agendar, gerenciar webhooks. | | **Somente leitura** | `read` | Só requisições `GET`. Qualquer outro método (`POST`, `PATCH`, `DELETE`) devolve `403` com o código `read_only_key`. | No [servidor MCP](/mcp/visao-geral/), uma chave somente leitura só expõe as ferramentas de consulta. É a escolha certa para dashboards, relatórios e para **explorar a API com uma IA** sem risco de ela mandar mensagens. ## Validade e revogação * **Validade:** quando a data passa, a chave deixa de funcionar e a API responde `401 expired_api_key`. * **Revogar:** no cartão **Chaves de API**, clique em **Revogar chave**. O efeito é imediato e não tem volta: a API passa a responder `401 revoked_api_key`. * O painel mostra quando cada chave foi **usada pela última vez** — útil para achar chaves esquecidas. Revogue a chave na hora se ela vazar (por exemplo, se foi parar num repositório público ou num print). ## A chave nunca vai para o navegador Importante A chave dá acesso ao WhatsApp da empresa: com ela, qualquer pessoa pode ler conversas e mandar mensagens em seu nome. **Nunca** coloque a chave em código que roda no navegador ou no celular do usuário (React, Vue, Angular, HTML, JavaScript do site, app mobile, extensão de navegador). Por isso a API **não aceita chamadas do navegador** (não envia cabeçalhos CORS, de propósito). Se você vir um erro de CORS no console, não é bug: é a API protegendo a sua conta. A solução é sempre a mesma — chamar a API **de um backend**: | Onde está o seu app | Onde colocar a chamada à API | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Lovable / Supabase | Uma **Supabase Edge Function**, com a chave guardada como secret `TOPTALKING_API_KEY`. Veja o [guia do Lovable](/ia/lovable/). | | Next.js / v0 / Vercel | Um **Route Handler** (`app/api/.../route.ts`) ou Server Action, com a chave em variável de ambiente. Veja [Bolt, v0 e Replit](/ia/bolt-v0-replit/). | | Node, Python, PHP… | O seu servidor, com a chave em variável de ambiente. | | Sem código | **n8n, Make ou Zapier**, no nó/módulo HTTP. Veja [n8n, Make e Zapier](/ia/n8n-make-zapier/). | O fluxo seguro fica assim: ```text Navegador do usuário → seu backend (guarda a chave) → API da TopTalking (sem chave) Edge Function / rota / n8n ``` ### Exemplo: Supabase Edge Function como ponte O front-end chama a sua função (com o login do usuário, se houver); a função chama a TopTalking com a chave guardada como secret: * Edge Function (Deno) supabase/functions/toptalking-contatos/index.ts ```ts const TOPTALKING_API = 'https://platform.toptalking.com.br/v1'; const cors = { 'Access-Control-Allow-Origin': '*', // troque pelo domínio do seu app em produção 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }; Deno.serve(async (req) => { if (req.method === 'OPTIONS') return new Response('ok', { headers: cors }); // A chave fica só aqui, no servidor (Secrets da Supabase / Lovable Cloud). const apiKey = Deno.env.get('TOPTALKING_API_KEY'); if (!apiKey) return new Response('TOPTALKING_API_KEY não configurada', { status: 500, headers: cors }); const res = await fetch(`${TOPTALKING_API}/contacts?limit=20`, { headers: { Authorization: `Bearer ${apiKey}` }, }); return new Response(await res.text(), { status: res.status, headers: { ...cors, 'Content-Type': 'application/json' }, }); }); ``` * Front-end (React) src/lib/contatos.ts ```ts import { supabase } from '@/integrations/supabase/client'; // O navegador chama a SUA função, nunca a TopTalking direto. export async function listarContatos() { const { data, error } = await supabase.functions.invoke('toptalking-contatos'); if (error) throw error; return data; // { object: "list", data: [...], has_more, next_cursor } } ``` Não crie um “proxy aberto” A função acima só lista contatos. Evite funções que repassam **qualquer** caminho ou corpo para a TopTalking: qualquer visitante do seu site conseguiria usar a sua chave. Faça uma função por ação (ex.: “enviar boas-vindas”), valide os dados e, se o app tiver login, exija o usuário logado. ## Boas práticas com chaves * Guarde a chave em variável de ambiente ou cofre de segredos, nunca no código nem no Git. * Use **uma chave por integração**, com nome claro, e **Somente leitura** sempre que não precisar escrever. * Prefira chaves com **validade** para testes e integrações temporárias. * Para agentes de IA (MCP, GPTs), comece com uma chave **Somente leitura**. * Revogue chaves que não aparecem como usadas há muito tempo. ## Erros de autenticação | HTTP | `code` | Quando acontece | O que fazer | | ---- | ----------------- | ------------------------------------------------------------- | --------------------------------------------------------- | | 401 | `missing_api_key` | Nenhuma chave foi enviada. | Envie `Authorization: Bearer tt_live_...`. | | 401 | `invalid_api_key` | A chave não existe ou foi digitada errada. | Copie a chave de novo (sem espaços extras) ou crie outra. | | 401 | `revoked_api_key` | A chave foi revogada no painel. | Crie uma chave nova. | | 401 | `expired_api_key` | A validade da chave acabou. | Crie uma chave nova. | | 403 | `read_only_key` | Chave **Somente leitura** tentou `POST`, `PATCH` ou `DELETE`. | Use uma chave com **Acesso total**. | Veja todos os códigos em [Erros](/comecando/erros/). # Erros > Formato dos erros da API da TopTalking, todos os códigos (code) com o status HTTP, quando acontecem e o que fazer, e como usar o X-Request-Id. A API usa os status HTTP de sempre: `2xx` deu certo, `4xx` tem algo a corrigir na requisição e `5xx` é problema do nosso lado ou do WhatsApp. ## Formato Todo erro tem o mesmo corpo JSON: ```json { "error": { "type": "invalid_request_error", "code": "missing_field", "message": "Informe `to` ou `contact_id`.", "param": "to", "request_id": "req_6f1d2c3b4a5e" } } ``` | Campo | Descrição | | ------------ | --------------------------------------------------------------------------------------- | | `type` | Categoria do erro (veja abaixo). | | `code` | Código estável e específico. **Use este campo no seu código** para decidir o que fazer. | | `message` | Explicação em português para humanos. Pode mudar de texto; não compare strings com ele. | | `param` | Campo que causou o erro, quando houver (ex.: `to`, `send_at`). | | `request_id` | Identificador da requisição — o mesmo valor do cabeçalho `X-Request-Id`. | Toda resposta (de sucesso ou de erro) traz o cabeçalho **`X-Request-Id`**. Registre-o nos seus logs e envie para o suporte quando precisar de ajuda com uma chamada específica. ## Tipos (`type`) | `type` | Significado | | ----------------------- | ------------------------------------------------------------------- | | `invalid_request_error` | A requisição tem algum problema (JSON, campos, parâmetros). | | `authentication_error` | Chave ausente, inválida, revogada ou expirada. | | `permission_error` | A chave não tem permissão para a ação (ex.: chave somente leitura). | | `not_found_error` | O recurso ou a rota não existe. | | `conflict_error` | Conflito de estado ou de `Idempotency-Key`. | | `channel_error` | Problema com o canal de envio (ex.: WhatsApp desconectado). | | `rate_limit_error` | Limite de requisições por minuto excedido. | | `api_error` | Erro inesperado do nosso lado. | ## Códigos (`code`) | HTTP | `code` | Quando acontece | O que fazer | | ---------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `invalid_json` | O corpo não é um JSON válido. | Confira aspas, vírgulas e o cabeçalho `Content-Type: application/json`. | | 400 | `missing_field` | Faltou um campo obrigatório (`param` diz qual). | Envie o campo. Ex.: `to` ou `contact_id` em `POST /messages`. | | 400 | `invalid_field` | Um campo tem valor ou formato inválido (`param` diz qual). | Corrija o valor: UUID malformado, data no passado, texto longo demais, `limit` fora de 1–100… | | 400 | `invalid_phone` | O telefone não parece um número válido. | Envie com DDI e DDD, ex.: `5511999998888`. | | 401 | `missing_api_key` | Nenhuma chave foi enviada. | Envie `Authorization: Bearer tt_live_...`. | | 401 | `invalid_api_key` | Chave inexistente ou digitada errada. | Copie a chave de novo ou crie outra. | | 401 | `revoked_api_key` | A chave foi revogada. | Crie uma chave nova no painel. | | 401 | `expired_api_key` | A validade da chave acabou. | Crie uma chave nova no painel. | | 403 | `read_only_key` | Chave somente leitura tentou escrever (`POST`, `PATCH`, `DELETE`). | Use uma chave com acesso total. | | 404 | `resource_not_found` | O ID não existe (ou é de outra organização). | Confira o ID. Ex.: o contato foi apagado no painel. | | 404 | `route_not_found` | O caminho não existe. | Confira a URL: ela começa com `/v1/` e usa os nomes em inglês (`/contacts`, `/messages`…). | | 405 | `method_not_allowed` | O caminho existe, mas não aceita esse método. | Ex.: `PUT` em vez de `PATCH`. Veja a [referência](/referencia/visao-geral/). | | 409 | `idempotency_conflict` | A mesma `Idempotency-Key` foi usada com um corpo diferente. | Gere uma chave nova para cada operação diferente. Veja [Idempotência](/comecando/idempotencia/). | | 409 | `invalid_state` | A ação não é possível no estado atual do recurso. | Ex.: cancelar uma mensagem agendada que já foi enviada, gerar QR Code para um WhatsApp já conectado, editar uma mensagem depois de 15 minutos, pausar uma campanha que não está enviando. Busque o recurso e confira o `status`. | | 422 | `channel_unavailable` | Não há WhatsApp conectado para enviar (ou o canal escolhido está desconectado). Em campanhas: não há número do WhatsApp Oficial conectado. | Reconecte o canal com [`POST /channels/{channel_id}/connect`](/referencia/canais/#reconectar-um-whatsapp-desconectado) (ou no painel, em Configurações → Conexões). Para campanhas, conecte o WhatsApp Oficial no painel, em Campanhas. | | 422 | `limit_reached` | Um limite da conta foi atingido: o máximo de **conexões de WhatsApp do plano** (`POST /channels`) ou 10 webhooks por organização (`POST /webhooks`). | Remova itens que não usa mais (ou reconecte um canal existente em vez de criar outro). Para mais conexões, faça upgrade do plano no painel. | | 429 | `rate_limit_exceeded` | Mais de 120 requisições no mesmo minuto com a mesma chave. | Espere os segundos do cabeçalho `Retry-After`. Veja [Limites de uso](/comecando/limites/). | | 500 ou 502 | `internal_error` | Erro inesperado do nosso lado. | Tente de novo com espera progressiva. Se persistir, fale com o suporte com o `request_id`. | | 502 | `provider_error` | O WhatsApp recusou a operação. Num envio, a mensagem fica registrada com status `failed`. Ao conectar, o WhatsApp não devolveu o QR Code ou o código de pareamento. | Leia a `message`. Confira se o número tem WhatsApp e se a mídia é acessível. Não repita em loop. | | 503 | `service_unavailable` | A API está temporariamente indisponível. | Tente de novo com espera progressiva. Use `Idempotency-Key` em `POST`. | | 504 | `timeout` | A requisição demorou demais para ser respondida (ex.: o WhatsApp demorou para gerar o QR Code). | Tente de novo com a mesma `Idempotency-Key`: se a primeira tiver dado certo, você recebe a resposta original. | Repare que há dois `422` com `type` diferentes: `limit_reached` é `invalid_request_error` e `channel_unavailable` é `channel_error`. 422 limit_reached (conexões do plano) ```json { "error": { "type": "invalid_request_error", "code": "limit_reached", "message": "Seu plano permite 3 conexões de WhatsApp. Remova uma ou faça upgrade no painel.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Como tratar os erros no código * **Decida pelo `code`**, não pela `message`. * **Erros `4xx` não melhoram com retentativa** (exceto `429`): corrija a requisição. * **`429`, `500`, `503`, `504` e falhas de rede podem ser repetidos** com espera progressiva (ex.: 1 s, 2 s, 4 s…). Envie `Idempotency-Key` em `POST` para não duplicar nada. * **`502 provider_error`** é uma recusa do WhatsApp para aquela mensagem: repetir em sequência raramente ajuda e pode prejudicar o número. Exemplo (Node.js) ```js async function toptalking(path, options = {}) { const res = await fetch(`https://platform.toptalking.com.br/v1${path}`, { ...options, headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', ...options.headers, }, }); const body = await res.json(); if (!res.ok) { const { code, message, request_id } = body.error; const err = new Error(`[${res.status} ${code}] ${message} (request_id: ${request_id})`); err.status = res.status; err.code = code; err.retryAfter = Number(res.headers.get('Retry-After')) || null; throw err; } return body; } ``` # Limites de uso > Limite de 120 requisições por minuto por chave, cabeçalhos X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset e Retry-After, e outros limites da API da TopTalking. ## Requisições por minuto Cada chave de API pode fazer **120 requisições por minuto**. A contagem é por chave: duas integrações com chaves diferentes têm limites independentes. As respostas trazem o estado do limite nos cabeçalhos: | Cabeçalho | Exemplo | Significado | | ----------------------- | ------------ | ------------------------------------------------------- | | `X-RateLimit-Limit` | `120` | Requisições permitidas por minuto para esta chave. | | `X-RateLimit-Remaining` | `87` | Quantas ainda restam na janela atual. | | `X-RateLimit-Reset` | `1794407460` | Quando a janela reinicia, em Unix timestamp (segundos). | O mesmo estado aparece em [`GET /me`](/referencia/conta/), no objeto `rate_limit`. ## Quando o limite estoura A API responde `429` com o código `rate_limit_exceeded` e o cabeçalho **`Retry-After`** com quantos segundos esperar: ```http HTTP/1.1 429 Too Many Requests Retry-After: 18 X-RateLimit-Limit: 120 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1794407460 ``` ```json { "error": { "type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Limite de requisições por minuto excedido.", "request_id": "req_9a8b7c6d5e4f" } } ``` Espere o tempo do `Retry-After` e tente de novo: Retentativa respeitando o Retry-After (Node.js) ```js async function comRetentativa(fazerRequisicao, tentativas = 3) { for (let i = 0; i < tentativas; i++) { const res = await fazerRequisicao(); if (res.status !== 429) return res; const espera = Number(res.headers.get('Retry-After') ?? 1); await new Promise((r) => setTimeout(r, espera * 1000)); } throw new Error('Limite de requisições excedido várias vezes seguidas.'); } ``` ## Como gastar menos requisições * **Use webhooks em vez de consultar a API em loop.** Em vez de perguntar a cada segundo “chegou mensagem?”, deixe a TopTalking [avisar o seu sistema](/webhooks/visao-geral/). * **Peça páginas maiores** com `limit=100` quando for ler muitos itens. * **Guarde em cache** o que muda pouco, como [etapas do Kanban](/referencia/kanban/) e [canais](/referencia/canais/). * **Espalhe envios em lote** ao longo do tempo — é melhor para o limite e, principalmente, para a saúde do seu número de WhatsApp. Veja [Boas práticas no WhatsApp](/comecando/boas-praticas-whatsapp/). ## Outros limites | Item | Limite | | ------------------------------------- | ------------------- | | Chaves de API ativas | 25 por organização | | Webhooks | 10 por organização | | Itens por página (`limit`) | 1 a 100 (padrão 20) | | Texto de uma mensagem | 4096 caracteres | | Anexos de uma mensagem agendada | 10 | | Antecedência de uma mensagem agendada | até 365 dias | | Validade de uma `Idempotency-Key` | 24 horas | | Histórico de eventos de webhook | 30 dias | # Paginação > Como funciona a paginação por cursor da API da TopTalking (object list, data, has_more, next_cursor, limit e cursor) com exemplos para percorrer todas as páginas. Os endpoints que listam itens (contatos, mensagens, mensagens agendadas, entregas de webhook…) usam **paginação por cursor**. ## Formato da lista ```json { "object": "list", "data": [ { "id": "721bfe94-e549-4354-b90c-a8d679e9b271", "object": "contact", "name": "João Pereira", "phone": "5521987654321", "email": null, "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": null, "ai_enabled": true, "ai_agent_id": null, "assigned_to": null, "pinned": false, "unread_count": 0, "last_message_at": "2026-11-11T14:30:12Z", "last_message_preview": "Combinado, obrigado!", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-09-14T11:20:00Z", "updated_at": "2026-11-11T14:30:12Z" } ], "has_more": true, "next_cursor": "eyJ0IjoiMjAyNi0xMS0xMVQxNDozMDoxMloiLCJpZCI6IjcyMWJmZTk0In0" } ``` (Exemplo com `limit=1`: há mais itens, então `has_more` é `true` e `next_cursor` vem preenchido.) | Campo | Descrição | | ------------- | -------------------------------------------------------- | | `object` | Sempre `list`. | | `data` | Os itens desta página. | | `has_more` | `true` se existem mais itens depois desta página. | | `next_cursor` | Valor para buscar a próxima página, ou `null` na última. | ## Parâmetros | Parâmetro | Tipo | Descrição | | --------- | ------- | ------------------------------------------------- | | `limit` | inteiro | Itens por página, de **1 a 100**. Padrão: **20**. | | `cursor` | string | O `next_cursor` da página anterior. | O cursor é **opaco**: não tente montar nem interpretar o valor, só repasse o que a API devolveu. Mantenha os mesmos filtros (ex.: `search`, `stage_id`) ao pedir a próxima página. ## Percorrer todas as páginas * JavaScript ```js async function listarTodosOsContatos() { const contatos = []; let cursor = null; do { const url = new URL('https://platform.toptalking.com.br/v1/contacts'); url.searchParams.set('limit', '100'); if (cursor) url.searchParams.set('cursor', cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const page = await res.json(); if (!res.ok) throw new Error(page.error.message); contatos.push(...page.data); cursor = page.has_more ? page.next_cursor : null; } while (cursor); return contatos; } ``` * Python ```python import os import requests def listar_todos_os_contatos(): contatos, cursor = [], None headers = {"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"} while True: params = {"limit": 100} if cursor: params["cursor"] = cursor res = requests.get( "https://platform.toptalking.com.br/v1/contacts", headers=headers, params=params, timeout=30, ) res.raise_for_status() page = res.json() contatos.extend(page["data"]) if not page["has_more"]: return contatos cursor = page["next_cursor"] ``` * curl ```bash # Primeira página curl "https://platform.toptalking.com.br/v1/contacts?limit=100" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" # Próxima página: passe o next_cursor recebido curl "https://platform.toptalking.com.br/v1/contacts?limit=100&cursor=eyJ0IjoiMjAyNi0xMS0xMVQxNDozMDoxMloiLCJpZCI6IjcyMWJmZTk0In0" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` # Idempotência > Use o cabeçalho Idempotency-Key nas requisições POST da API da TopTalking para repetir chamadas com segurança, sem enviar mensagens duplicadas (essencial para agentes de IA e automações). Redes falham. Às vezes o seu servidor manda a requisição, a TopTalking envia a mensagem, mas a resposta se perde no caminho. Se você simplesmente repetir, o cliente recebe a mensagem **duas vezes**. O cabeçalho **`Idempotency-Key`** resolve isso: ele identifica a operação, e repetições com a mesma chave não são executadas de novo. ## Como funciona * Envie `Idempotency-Key: ` em requisições **`POST`** (é opcional). * Se a mesma chave chegar de novo **com o mesmo corpo em até 24 horas**, a API devolve **a resposta original guardada**, sem executar a ação de novo. * Se a mesma chave chegar com um **corpo diferente**, a API responde `409` com o código `idempotency_conflict`. * O valor pode ter até 255 caracteres. Depois de 24 horas, a chave pode ser reutilizada. ## Exemplo * curl ```bash curl https://platform.toptalking.com.br/v1/messages \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pedido-1234-saiu-para-entrega" \ -d '{ "to": "5511999998888", "text": "Seu pedido #1234 saiu para entrega." }' ``` * JavaScript ```js await fetch('https://platform.toptalking.com.br/v1/messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', // Mesmo pedido + mesmo evento = mesma chave, em qualquer retentativa. 'Idempotency-Key': `pedido-${pedido.id}-saiu-para-entrega`, }, body: JSON.stringify({ to: pedido.telefone, text: `Seu pedido #${pedido.id} saiu para entrega.`, }), }); ``` ## Como escolher a chave A chave deve representar **a intenção**, não a tentativa: | Situação | Boa chave | Evite | | ----------------------------------- | --------------------------------------- | --------------------------------------------------- | | Aviso de pedido despachado | `pedido-1234-despachado` | Um UUID novo a cada tentativa (não protege nada) | | Boas-vindas a um lead do formulário | `lead--boas-vindas` | O horário atual | | Lembrete agendado de consulta | `consulta-987-lembrete-24h` | A mesma chave para clientes diferentes (gera `409`) | | Fluxo do n8n com retentativa | `n8n--<índice do item>` | Uma chave fixa para todo o fluxo | Se a sua operação não tem um ID natural, gere um UUID **uma vez**, guarde junto com a tarefa e reutilize-o em todas as tentativas. ## Quando usar * **Sempre** em `POST /messages` e `POST /scheduled-messages` feitos por automações, filas ou agentes de IA — eles costumam repetir chamadas quando algo demora. * Em `POST /contacts` e `POST /webhooks`, quando o seu código faz retentativas automáticas. # Boas práticas no WhatsApp > Como enviar mensagens pela API da TopTalking sem arriscar o banimento do número de WhatsApp - opt-in, ritmo de envio, conteúdo e quando usar Campanhas. A conexão de WhatsApp da TopTalking por QR code usa o **WhatsApp do seu celular** — não é a API oficial da Meta. Isso dá liberdade, mas exige cuidado: **o WhatsApp pode banir números que enviam mensagens em massa ou não solicitadas**, e um número banido perde as conversas e o histórico com os clientes. Risco real de banimento Não use `POST /messages` para disparos em massa, listas compradas ou mensagens para quem nunca falou com a sua empresa. Para comunicação em massa, use **[Campanhas](/referencia/campanhas/)**, que saem pelo WhatsApp Oficial (API da Meta) com templates aprovados — pelo painel ou pela API. ## Envie só para quem pediu * **Opt-in:** mande mensagens apenas para quem deu o número e aceitou receber contato (formulário com consentimento, compra, conversa iniciada pelo cliente). * **Contexto claro:** a mensagem deve deixar óbvio por que a pessoa está recebendo (ex.: “Olá, Maria! Aqui é a Loja Aurora, sobre o seu pedido #1234…”). * **Saída fácil:** respeite quem pedir para não receber mais. Uma boa prática é [arquivar](/referencia/contatos/#atualizar-contato-e-mover-no-kanban) o contato ou marcá-lo no seu sistema para não entrar em novos envios. ## Ritmo de envio * **Espalhe os envios.** Nada de 500 mensagens no mesmo minuto. Coloque um intervalo entre as mensagens (vários segundos, com alguma variação) e prefira enviar ao longo do dia. * **Comece devagar** com números novos ou recém-conectados. * **Prefira responder a iniciar.** Mensagens que respondem a uma conversa do cliente são as mais seguras. * **Evite textos idênticos** para muitas pessoas: personalize com nome e contexto. ## Conteúdo * Mensagens curtas, úteis e esperadas (confirmação, status, lembrete, resposta). * Cuidado com links encurtados e muitos links por mensagem. * Use a formatação do WhatsApp com moderação: `*negrito*`, `_itálico_`, `~tachado~`. * Arquivos e imagens: use URLs HTTPS públicas e estáveis. ## Sinais de alerta Pare e revise a estratégia se: * muitas mensagens ficam em `sent` e nunca chegam a `delivered`; * os envios começam a falhar com `502 provider_error`; * clientes reclamam ou bloqueiam o número; * o WhatsApp desconecta com frequência (a API passa a responder `422 channel_unavailable`). ## Quando usar Campanhas em vez da API | Objetivo | Use | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Mensagem transacional para um cliente (pedido, boleto, lembrete) | **API** — `POST /messages` ou `POST /scheduled-messages` | | Responder conversas, automações de atendimento | **API** ou **agentes de IA** do painel | | Promoção, novidade ou aviso para muitos contatos de uma vez | **Campanhas** pelo WhatsApp Oficial, com template aprovado — no painel ou pela API ([Campanhas](/referencia/campanhas/)) | As mensagens enviadas pela API aparecem no chat do painel como qualquer outra, então a equipe sempre vê o que foi enviado. # Referência da API > Visão geral da API REST da TopTalking - URL base, cabeçalhos, erros comuns e a lista completa dos 47 endpoints (conta, canais e conexões por QR Code, contatos, mensagens, grupos, Kanban, mensagens agendadas, equipe, agentes de IA, campanhas e webhooks). ## URL base e cabeçalhos ```text https://platform.toptalking.com.br/v1 ``` | Cabeçalho | Quando | Valor | | ----------------- | --------------------------------------- | --------------------------------------------------------------------------- | | `Authorization` | Sempre | `Bearer tt_live_...` (ou use `X-Api-Key: tt_live_...`) | | `Content-Type` | Requisições com corpo (`POST`, `PATCH`) | `application/json` | | `Idempotency-Key` | Opcional, em `POST` que cria algo | Um valor único por operação. Veja [Idempotência](/comecando/idempotencia/). | Toda resposta traz `X-Request-Id` e os cabeçalhos de [limite de uso](/comecando/limites/). Erros seguem o [formato padrão](/comecando/erros/) e listas usam [paginação por cursor](/comecando/paginacao/). ### Erros que qualquer rota pode responder Cada endpoint lista os **seus** erros específicos. Além deles, qualquer rota pode responder: | HTTP | `code` | Quando acontece | | -------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | 401 | `missing_api_key`, `invalid_api_key`, `revoked_api_key`, `expired_api_key` | Problema com a chave. | | 403 | `read_only_key` | Chave somente leitura em qualquer método que não seja `GET`. | | 429 | `rate_limit_exceeded` | Mais de 120 requisições por minuto (respeite `Retry-After`). | | 500, 502 | `internal_error` | Falha inesperada ou passageira. Tente de novo. | | 503 | `service_unavailable` | API temporariamente indisponível. Tente de novo. | | 504 | `timeout` | A requisição demorou demais. Tente de novo com a mesma `Idempotency-Key`. | ### Sobre os exemplos Cada endpoint tem exemplos em **curl**, **JavaScript** (`fetch` nativo: Node 18+, Deno, Bun, Edge Functions) e **Python** (biblioteca `requests`). Todos leem a chave da variável de ambiente `TOPTALKING_API_KEY` e devem rodar **no seu backend** — nunca no navegador. Em Supabase Edge Functions, troque `process.env.TOPTALKING_API_KEY` por `Deno.env.get('TOPTALKING_API_KEY')`. Os exemplos usam sempre os mesmos dados fictícios: a organização **Loja Aurora**, o WhatsApp **Vendas** (`d781e759-…`), a cliente **Maria Souza** (`3f1c2b9a-…`, `5511999998888`) e a atendente **Carla Mendes** (`4083deb8-…`). ## Teste direto na documentação Todo endpoint da referência tem o botão **Testar**, ao lado do método e do caminho. Ele abre um formulário com os parâmetros e o corpo daquela rota, envia a requisição **de verdade** para a API e mostra a resposta real: status, tempo, cabeçalhos (`X-Request-Id`, `X-RateLimit-*`) e o JSON. 1. Clique em **Testar** no endpoint. 2. Cole a sua chave de API (gere em [Configurações → Desenvolvedor](https://toptalking.com.br/configuracoes/desenvolvedor)). Ela vale para todos os testes da aba. 3. Preencha os parâmetros, ajuste o corpo e clique em **Enviar requisição**. O painel também mostra a mesma requisição em **curl**, com os valores que você preencheu, pronta para copiar. As chamadas são reais O teste usa a sua conta de verdade. `POST /messages` **envia a mensagem** pelo seu WhatsApp, `DELETE` remove de fato e `POST /campaigns` cria uma campanha. Para explorar sem risco, use uma chave **Somente leitura**: ela só executa consultas (`GET`) e recebe `403 read_only_key` no resto. * **Onde fica a chave:** só na memória da aba do navegador. Ela some quando você fecha a aba e só é enviada para a API da TopTalking. * **Exemplos seguros:** telefones e URLs dos exemplos vêm como `SEU_NUMERO_COM_DDD` e `SEU-DOMINIO`. Enquanto você não trocar pelos seus dados, a API recusa a requisição, então um clique sem querer não manda mensagem para ninguém. * **Rotas que apagam** pedem um segundo clique de confirmação. * **Sem chave** a requisição também é enviada, e você vê como a API responde nesse caso (`401 missing_api_key`). Isso não muda a regra para o seu código: no seu site ou app, a chave continua **só no backend**. O teste funciona aqui porque a documentação e a API ficam no mesmo endereço. ## Endpoints ### Conta | Método | Caminho | O que faz | | ------ | --------------------------------------------------------------------- | ------------------------------------------------ | | `GET` | [`/me`](/referencia/conta/#dados-da-chave-e-da-organiza%C3%A7%C3%A3o) | Dados da chave e da organização (teste da chave) | ### Canais e conexões | Método | Caminho | O que faz | | -------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | `GET` | [`/channels`](/referencia/canais/#listar-canais) | Listar canais (WhatsApp e Chat do site) e o status de cada um | | `POST` | [`/channels`](/referencia/canais/#criar-canal) | Criar canal e iniciar a conexão de um WhatsApp (devolve o QR Code) | | `GET` | [`/channels/{channel_id}`](/referencia/canais/#buscar-canal) | Buscar canal | | `PATCH` | [`/channels/{channel_id}`](/referencia/canais/#renomear-canal) | Renomear canal | | `DELETE` | [`/channels/{channel_id}`](/referencia/canais/#remover-canal) | Remover canal de WhatsApp | | `POST` | [`/channels/{channel_id}/connect`](/referencia/canais/#gerar-qr-code-ou-c%C3%B3digo-de-pareamento) | Gerar novo QR Code ou código de pareamento (reconectar) | | `GET` | [`/channels/{channel_id}/qr`](/referencia/canais/#consultar-o-qr-code) | QR Code atual e status da conexão (consulte a cada 3–5 s) | | `POST` | [`/channels/{channel_id}/disconnect`](/referencia/canais/#desconectar-canal) | Desconectar o WhatsApp | Passo a passo: [Conectar um WhatsApp por QR Code](/referencia/canais/#conectar-um-whatsapp-por-qr-code). ### Contatos | Método | Caminho | O que faz | | ------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | `GET` | [`/contacts`](/referencia/contatos/#listar-contatos) | Listar e buscar contatos (filtros por etapa, canal, grupo, responsável…) | | `POST` | [`/contacts`](/referencia/contatos/#criar-ou-atualizar-contato) | Criar ou atualizar contato pelo telefone | | `GET` | [`/contacts/{contact_id}`](/referencia/contatos/#buscar-contato) | Buscar contato | | `PATCH` | [`/contacts/{contact_id}`](/referencia/contatos/#atualizar-contato-e-mover-no-kanban) | Atualizar contato, mover no Kanban, atribuir, fixar, escolher agente, ligar/desligar IA, finalizar, arquivar | | `GET` | [`/contacts/{contact_id}/messages`](/referencia/contatos/#hist%C3%B3rico-da-conversa) | Histórico da conversa | | `POST` | [`/contacts/{contact_id}/read`](/referencia/contatos/#marcar-conversa-como-lida) | Marcar conversa como lida | ### Mensagens | Método | Caminho | O que faz | | -------- | --------------------------------------------------------------------------------- | ------------------------------------------------------ | | `POST` | [`/messages`](/referencia/mensagens/#enviar-mensagem) | Enviar mensagem (texto e/ou mídia), também para grupos | | `GET` | [`/messages/{message_id}`](/referencia/mensagens/#buscar-mensagem) | Buscar mensagem e status de entrega | | `PATCH` | [`/messages/{message_id}`](/referencia/mensagens/#editar-mensagem) | Editar mensagem enviada (até 15 min) | | `DELETE` | [`/messages/{message_id}`](/referencia/mensagens/#apagar-mensagem) | Apagar para todos (até 48 h) ou só no painel | | `POST` | [`/messages/{message_id}/reaction`](/referencia/mensagens/#reagir-a-uma-mensagem) | Reagir (ou remover a reação) com emoji | ### Grupos | Método | Caminho | O que faz | | ------ | ------------------------------------------------------------------------------- | ---------------------------------------------------- | | `GET` | [`/groups`](/referencia/grupos/#listar-grupos) | Listar grupos do WhatsApp (o `id` é um `contact_id`) | | `GET` | [`/groups/{group_id}/participants`](/referencia/grupos/#participantes-do-grupo) | Participantes e administradores do grupo | ### Kanban | Método | Caminho | O que faz | | ------ | ---------------------------------------------- | ------------------------------------------------------------------------------ | | `GET` | [`/stages`](/referencia/kanban/#listar-etapas) | Listar etapas do Kanban (para mover cards, use `PATCH /contacts/{contact_id}`) | ### Mensagens agendadas | Método | Caminho | O que faz | | -------- | ----------------------------------------------------------------------------------------------------------- | -------------------------- | | `GET` | [`/scheduled-messages`](/referencia/mensagens-agendadas/#listar-mensagens-agendadas) | Listar mensagens agendadas | | `POST` | [`/scheduled-messages`](/referencia/mensagens-agendadas/#agendar-mensagem) | Agendar mensagem | | `GET` | [`/scheduled-messages/{scheduled_message_id}`](/referencia/mensagens-agendadas/#buscar-mensagem-agendada) | Buscar mensagem agendada | | `DELETE` | [`/scheduled-messages/{scheduled_message_id}`](/referencia/mensagens-agendadas/#cancelar-mensagem-agendada) | Cancelar mensagem agendada | ### Equipe | Método | Caminho | O que faz | | ------ | ----------------------------------------------------------- | ------------------------ | | `GET` | [`/members`](/referencia/equipe/#listar-membros) | Listar membros da equipe | | `GET` | [`/members/{member_id}`](/referencia/equipe/#buscar-membro) | Buscar membro | Para atribuir uma conversa, veja [Atribuir uma conversa](/referencia/equipe/#atribuir-uma-conversa). ### Agentes de IA | Método | Caminho | O que faz | | ------- | --------------------------------------------------------------------------------- | ------------------------------ | | `GET` | [`/ai-agents`](/referencia/agentes-ia/#listar-agentes-de-ia) | Listar agentes de IA | | `GET` | [`/ai-agents/{agent_id}`](/referencia/agentes-ia/#buscar-agente-de-ia) | Buscar agente de IA | | `PATCH` | [`/ai-agents/{agent_id}`](/referencia/agentes-ia/#ligar-ou-desligar-agente-de-ia) | Ligar ou desligar agente de IA | ### Campanhas | Método | Caminho | O que faz | | ------ | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | `GET` | [`/templates`](/referencia/campanhas/#listar-templates) | Listar templates do WhatsApp Oficial (`ready: true` = pode usar) | | `POST` | [`/campaigns/quote`](/referencia/campanhas/#simular-pre%C3%A7o) | Simular o preço de uma campanha | | `POST` | [`/campaigns`](/referencia/campanhas/#criar-campanha) | Criar campanha (vai para aprovação) | | `GET` | [`/campaigns`](/referencia/campanhas/#listar-campanhas) | Listar campanhas | | `GET` | [`/campaigns/{campaign_id}`](/referencia/campanhas/#buscar-campanha) | Buscar campanha (status, contadores e preço) | | `GET` | [`/campaigns/{campaign_id}/recipients`](/referencia/campanhas/#destinat%C3%A1rios-da-campanha) | Status de cada destinatário | | `POST` | [`/campaigns/{campaign_id}/pause`](/referencia/campanhas/#pausar-campanha) | Pausar campanha | | `POST` | [`/campaigns/{campaign_id}/resume`](/referencia/campanhas/#retomar-campanha) | Retomar campanha | ### Webhooks (gestão) | Método | Caminho | O que faz | | -------- | --------------------------------------------------------------------------------------- | ----------------------------------------------- | | `GET` | [`/webhooks`](/referencia/webhooks/#listar-webhooks) | Listar webhooks | | `POST` | [`/webhooks`](/referencia/webhooks/#criar-webhook) | Criar webhook | | `GET` | [`/webhooks/{webhook_id}`](/referencia/webhooks/#buscar-webhook) | Buscar webhook | | `PATCH` | [`/webhooks/{webhook_id}`](/referencia/webhooks/#atualizar-webhook) | Atualizar webhook (URL, eventos, ativar/pausar) | | `DELETE` | [`/webhooks/{webhook_id}`](/referencia/webhooks/#excluir-webhook) | Excluir webhook | | `POST` | [`/webhooks/{webhook_id}/test`](/referencia/webhooks/#enviar-evento-de-teste) | Enviar evento de teste (`ping`) | | `GET` | [`/webhooks/{webhook_id}/deliveries`](/referencia/webhooks/#hist%C3%B3rico-de-entregas) | Histórico de entregas | ## Objetos Todo objeto tem o campo `object` (o tipo) e, quase sempre, um `id` (UUID): | `object` | Página | | -------------------------------------------------------------- | ------------------------------------------------------- | | `account` | [Conta](/referencia/conta/) | | `channel`, `channel_connection` | [Canais e conexões](/referencia/canais/) | | `contact` | [Contatos](/referencia/contatos/) | | `message` | [Mensagens](/referencia/mensagens/) | | `group`, `group_participant` | [Grupos](/referencia/grupos/) | | `stage` | [Kanban](/referencia/kanban/) | | `scheduled_message` | [Mensagens agendadas](/referencia/mensagens-agendadas/) | | `member` | [Equipe](/referencia/equipe/) | | `ai_agent` | [Agentes de IA](/referencia/agentes-ia/) | | `template`, `campaign`, `campaign_quote`, `campaign_recipient` | [Campanhas](/referencia/campanhas/) | | `webhook_endpoint`, `webhook_delivery` | [Webhooks (gestão)](/referencia/webhooks/) | | `event` | [Eventos de webhook](/webhooks/eventos/) | | `list` | [Paginação](/comecando/paginacao/) | Campos sem valor vêm como `null` (não são omitidos). Datas seguem ISO 8601 e telefones vêm só com dígitos, com DDI. ## Contrato OpenAPI A especificação completa (OpenAPI 3.1), com todos os schemas e os payloads dos webhooks, está publicada para você importar no Postman, Insomnia, Bruno, geradores de SDK ou no ChatGPT: [openapi.yaml](/openapi.yaml)https\://platform.toptalking.com.br/openapi.yaml [openapi.json](/openapi.json)https\://platform.toptalking.com.br/openapi.json Veja como usar em [OpenAPI](/recursos/openapi/). # Conta > GET /me da API da TopTalking - devolve a organização, a chave de API em uso (nível de acesso) e o estado do limite de requisições. Ideal para testar a chave. ## Dados da chave e da organização GET`/v1/me`Testar Devolve a organização dona da chave, os dados da chave usada na requisição e o estado do limite de requisições. Use para **testar se a chave funciona** e para saber se ela é somente leitura. Não tem parâmetros. * curl ```bash curl https://platform.toptalking.com.br/v1/me \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/me', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const conta = await res.json(); console.log(conta.organization.name, conta.api_key.access); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/me", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() conta = res.json() print(conta["organization"]["name"], conta["api_key"]["access"]) ``` #### Resposta `200` ```json { "object": "account", "organization": { "id": "e044a615-e879-44bb-8510-948b10b5e93c", "name": "Loja Aurora" }, "api_key": { "id": "508a23eb-5e24-4997-b928-3fce4f0f5593", "name": "Integração do site", "prefix": "tt_live_a1b2c3d4", "access": "full", "created_at": "2026-11-11T13:02:11Z" }, "rate_limit": { "limit": 120, "remaining": 119, "reset_at": "2026-11-11T14:31:00Z" } } ``` | Campo | Tipo | Descrição | | ---------------------- | ------- | -------------------------------------------------------------------------------------- | | `object` | string | Sempre `account`. | | `organization.id` | UUID | ID da organização. | | `organization.name` | string | Nome da organização. | | `api_key.id` | UUID | ID da chave. | | `api_key.name` | string | Nome dado à chave no painel. | | `api_key.prefix` | string | Início da chave (ex.: `tt_live_a1b2c3d4`), para identificar sem expor a chave inteira. | | `api_key.access` | string | `full` (acesso total) ou `read` (somente leitura). | | `api_key.created_at` | data | Quando a chave foi criada. | | `rate_limit.limit` | inteiro | Requisições permitidas por minuto (120). | | `rate_limit.remaining` | inteiro | Quantas ainda restam na janela atual. | | `rate_limit.reset_at` | data | Quando a janela reinicia. | #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | 401 | `missing_api_key`, `invalid_api_key`, `revoked_api_key`, `expired_api_key` | Problema com a chave. Veja [Autenticação](/comecando/autenticacao/#erros-de-autentica%C3%A7%C3%A3o). | | 429 | `rate_limit_exceeded` | Limite de requisições excedido. | 401 invalid_api_key ```json { "error": { "type": "authentication_error", "code": "invalid_api_key", "message": "Chave de API inválida.", "request_id": "req_6f1d2c3b4a5e" } } ``` # Canais e conexões > Conecte um WhatsApp pela API da TopTalking - crie o canal (POST /channels), mostre o QR Code (GET /channels/{channel_id}/qr) até o status virar connected, ou use o código de pareamento. Também reconectar, renomear, desconectar e remover canais. Um **canal** é por onde as conversas acontecem. Todo contato, mensagem e grupo pertence a um canal. Antes de enviar a primeira mensagem, a organização precisa de pelo menos um WhatsApp **conectado** (`status: "connected"`). ## Tipos de canal | `type` | O que é | O que dá para fazer pela API | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `whatsapp` | Um número de WhatsApp conectado lendo um **QR Code** (ou digitando um código de pareamento) no celular — o mesmo que Configurações → Conexões no painel. | Tudo: [criar e conectar](#conectar-um-whatsapp-por-qr-code), [reconectar](#reconectar-um-whatsapp-desconectado), [renomear](#renomear-canal), [desconectar](#desconectar-canal) e [remover](#remover-canal). | | `webchat` | O **Chat do site**, o widget de conversa instalado no seu site. | Aparece em [`GET /channels`](#listar-canais) e pode ser [renomeado](#renomear-canal). Criar, configurar e remover é só no painel. | ## Status da conexão | `status` | Significado | O que fazer | | -------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `connected` | O celular leu o QR Code (ou digitou o código). **Pronto para enviar e receber.** | Nada: use o canal normalmente. | | `connecting` | Esperando a leitura do QR Code ou do código de pareamento, ou reconectando sozinho. | Mostre o QR Code e consulte [`GET /channels/{channel_id}/qr`](#consultar-o-qr-code) a cada 3–5 s. | | `disconnected` | Sem sessão do WhatsApp. Envios por este canal respondem `422 channel_unavailable`. | Gere um novo QR Code com [`POST /channels/{channel_id}/connect`](#gerar-qr-code-ou-c%C3%B3digo-de-pareamento). | ```text POST /channels │ ▼ ┌────────────┐ o celular leu o QR Code ┌───────────┐ │ connecting │ ────────────────────────► │ connected │ └────────────┘ (ou digitou o código) └───────────┘ ▲ │ ▲ │ │ └───┘ QR Code novo a cada ~20–40 s │ POST /channels/{id}/disconnect │ (consulte GET /channels/{id}/qr) │ ou a sessão caiu no celular │ ▼ │ POST /channels/{id}/connect ┌──────────────┐ └─────────────────────────────────────│ disconnected │ └──────────────┘ ``` ## Conectar um WhatsApp por QR Code O fluxo é sempre o mesmo: **criar o canal → mostrar o QR Code → atualizar a cada poucos segundos → parar quando conectar.** Tudo pelo **seu backend** (a chave de API nunca vai para o navegador). 1. **Crie o canal.** Chame [`POST /channels`](#criar-canal) com um nome. A resposta `201` traz o ID do canal em `channel.id` e o primeiro QR Code em `qr_code`. ```bash curl https://platform.toptalking.com.br/v1/channels \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: conexao-pos-venda-001" \ -d '{ "name": "Pós-venda" }' ``` ```json { "object": "channel_connection", "channel": { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda", "phone": null, "status": "connecting", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" }, "status": "connecting", "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARQAAAEUCAYAAADqcMl5AAAAAklEQVR4AewaftIAAA...", "qr_code_generated_at": "2026-11-11T15:20:02Z", "pairing_code": null } ``` Guarde o `channel.id`: você vai usá-lo nos próximos passos. 2. **Mostre o QR Code.** `qr_code` é uma imagem PNG em *data URL* (`data:image/png;base64,...`). Não precisa converter: coloque direto no `src` de uma imagem — `` no React ou `img.src = qr_code` em JavaScript puro. No celular que vai ser conectado: **WhatsApp → Configurações** (no Android, toque em ⋮) **→ Aparelhos conectados → Conectar um aparelho** e aponte a câmera para o QR Code. 3. **Atualize a cada 3–5 segundos.** O QR Code **vale só \~20 a 40 segundos** e é renovado automaticamente. Consulte [`GET /channels/{channel_id}/qr`](#consultar-o-qr-code) em intervalos curtos e troque a imagem sempre que `qr_code` mudar (`qr_code_generated_at` diz quando o atual foi gerado). ```bash curl https://platform.toptalking.com.br/v1/channels/742539b3-2c69-4acc-988f-fcd836e565a9/qr \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` Esta rota só **lê** o QR Code atual — não gera um pareamento novo. Pode consultar à vontade (respeitando o [limite de 120 requisições por minuto](/comecando/limites/)). 4. **Pare quando `status` for `connected`.** Assim que o celular lê o código, a consulta devolve `status: "connected"`, `qr_code: null` e o número em `channel.phone`: ```json { "object": "channel_connection", "channel": { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda", "phone": "5511955554444", "status": "connected", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" }, "status": "connected", "qr_code": null, "qr_code_generated_at": null, "pairing_code": null } ``` Prefere não consultar? Assine o webhook [`channel.status_updated`](/webhooks/eventos/#channelstatus_updated): ele chega no instante em que o canal conecta. 5. **Envie uma mensagem de teste.** Use o ID do canal em `channel_id` para garantir que a mensagem sai pelo número recém-conectado (mande para o seu próprio celular): ```bash curl https://platform.toptalking.com.br/v1/messages \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: teste-pos-venda-001" \ -d '{ "to": "5511999998888", "channel_id": "742539b3-2c69-4acc-988f-fcd836e565a9", "text": "Teste: o WhatsApp do Pós-venda está conectado ✅" }' ``` Se ninguém ler o QR Code Se o QR Code não for lido por alguns minutos, o pareamento pode parar: a consulta passa a devolver `status: "disconnected"` com `qr_code: null`. Nesse caso, gere outro com [`POST /channels/{channel_id}/connect`](#gerar-qr-code-ou-c%C3%B3digo-de-pareamento) — **não crie outro canal**, ou você vai gastar conexões do plano. ### Exemplo completo Um backend que cria a conexão e acompanha o QR Code até conectar. Escolha a linguagem: * curl conectar-whatsapp.sh ```bash # Requer curl, jq e base64. Salva o QR Code atual em qr.png até conectar. API=https://platform.toptalking.com.br/v1 AUTH="Authorization: Bearer $TOPTALKING_API_KEY" # 1. Cria o canal e guarda o ID ID=$(curl -s "$API/channels" -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: conexao-pos-venda-001" \ -d '{ "name": "Pós-venda" }' | jq -r '.channel.id') echo "Canal criado: $ID" # 2 a 4. A cada 4 s: salva o QR Code (se houver) e para quando conectar while true; do R=$(curl -s "$API/channels/$ID/qr" -H "$AUTH") STATUS=$(echo "$R" | jq -r '.status') [ "$STATUS" = "connected" ] && echo "Conectado: $(echo "$R" | jq -r '.channel.phone')" && break QR=$(echo "$R" | jq -r '.qr_code // empty') [ -n "$QR" ] && echo "${QR#*,}" | base64 -d > qr.png && echo "status: $STATUS (abra qr.png)" sleep 4 done ``` * JavaScript toptalking-conexao.ts ```ts // Node 18+ (fetch nativo). Roda no SEU servidor: a chave nunca vai para o navegador. const API = 'https://platform.toptalking.com.br/v1'; export type Conexao = { object: 'channel_connection'; channel: { id: string; name: string; phone: string | null; status: string }; status: 'connected' | 'connecting' | 'disconnected'; qr_code: string | null; // "data:image/png;base64,..." → direto no qr_code_generated_at: string | null; pairing_code: string | null; }; async function api(path: string, init: RequestInit = {}): Promise { const res = await fetch(`${API}${path}`, { ...init, headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', ...init.headers, }, }); const body = await res.json(); if (!res.ok) throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`); return body as T; } /** Passo 1: cria o canal. O primeiro QR Code pode vir null nos primeiros segundos. */ export function criarConexao(nome: string, idempotencyKey = crypto.randomUUID()) { return api('/channels', { method: 'POST', headers: { 'Idempotency-Key': idempotencyKey }, body: JSON.stringify({ name: nome }), }); } /** Passo 3: lê o QR Code atual e o status (não gera um pareamento novo). */ export function consultarConexao(channelId: string) { return api(`/channels/${channelId}/qr`); } /** Gera um QR Code novo para um canal desconectado (ou que parou de parear). */ export function gerarNovoQr(channelId: string) { return api(`/channels/${channelId}/connect`, { method: 'POST', body: '{}' }); } /** Passos 2 a 4: repete a consulta a cada 4 s até conectar (desiste depois de 3 min). */ export async function aguardarConexao( channelId: string, aoMudarQr: (qrCode: string) => void, { intervaloMs = 4000, limiteMs = 3 * 60_000 } = {}, ): Promise { const fim = Date.now() + limiteMs; let ultimoQr: string | null = null; while (Date.now() < fim) { let conexao = await consultarConexao(channelId); if (conexao.status === 'connected') return conexao; // pronto! if (conexao.status === 'disconnected' && !conexao.qr_code) { conexao = await gerarNovoQr(channelId); // o pareamento parou: gera outro } if (conexao.qr_code && conexao.qr_code !== ultimoQr) { ultimoQr = conexao.qr_code; aoMudarQr(conexao.qr_code); // QR Code novo: atualize a tela } await new Promise((r) => setTimeout(r, intervaloMs)); } throw new Error('O QR Code não foi lido a tempo. Tente de novo.'); } // Uso num script: // const { channel } = await criarConexao('Pós-venda'); // const pronto = await aguardarConexao(channel.id, (qr) => console.log('QR novo:', qr.slice(0, 40))); // console.log('Conectado:', pronto.channel.phone); ``` * Python conectar_whatsapp.py ```python import base64 import os import time import requests API = "https://platform.toptalking.com.br/v1" HEADERS = {"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"} def salvar_qr(qr_code: str, arquivo: str = "qr.png") -> None: # qr_code = "data:image/png;base64,iVBORw0..." -> grava o PNG with open(arquivo, "wb") as f: f.write(base64.b64decode(qr_code.split(",", 1)[1])) # Passo 1: cria o canal res = requests.post( f"{API}/channels", headers={**HEADERS, "Idempotency-Key": "conexao-pos-venda-001"}, json={"name": "Pós-venda"}, timeout=30, ) res.raise_for_status() conexao = res.json() channel_id = conexao["channel"]["id"] # Passos 2 a 4: a cada 4 s, atualiza o QR Code até conectar (no máximo 3 min) ultimo_qr = None limite = time.time() + 180 while time.time() < limite: if conexao["status"] == "connected": print("Conectado:", conexao["channel"]["phone"]) break if conexao["status"] == "disconnected" and not conexao["qr_code"]: # o pareamento parou: gera outro QR Code conexao = requests.post( f"{API}/channels/{channel_id}/connect", headers=HEADERS, json={}, timeout=30 ).json() if conexao["qr_code"] and conexao["qr_code"] != ultimo_qr: ultimo_qr = conexao["qr_code"] salvar_qr(ultimo_qr) print("QR Code novo salvo em qr.png: leia com o celular.") time.sleep(4) res = requests.get(f"{API}/channels/{channel_id}/qr", headers=HEADERS, timeout=30) res.raise_for_status() conexao = res.json() else: print("O QR Code não foi lido a tempo. Tente de novo.") ``` #### Mostrando o QR Code numa tela Para o usuário conectar pelo **seu** sistema, exponha duas rotas no seu backend (que chamam a TopTalking com a chave) e faça a tela consultar **essas rotas** — nunca a API da TopTalking direto do navegador. server.ts (Express 5) ```ts import express from 'express'; import { criarConexao, consultarConexao, type Conexao } from './toptalking-conexao'; const app = express(); app.use(express.json()); // Devolva ao navegador só o necessário (nunca a chave). const resumo = (c: Conexao) => ({ id: c.channel.id, status: c.status, qrCode: c.qr_code, telefone: c.channel.phone, }); // Proteja estas rotas com o login do seu sistema. app.post('/api/whatsapp/conexoes', async (req, res) => { const conexao = await criarConexao(req.body.nome ?? 'WhatsApp'); res.status(201).json(resumo(conexao)); }); app.get('/api/whatsapp/conexoes/:id', async (req, res) => { res.json(resumo(await consultarConexao(req.params.id))); }); app.listen(3000); ``` * React ConectarWhatsApp.tsx ```tsx import { useEffect, useState } from 'react'; type Conexao = { id: string; status: string; qrCode: string | null; telefone: string | null }; export function ConectarWhatsApp() { const [conexao, setConexao] = useState(null); async function iniciar() { const res = await fetch('/api/whatsapp/conexoes', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nome: 'Pós-venda' }), }); setConexao(await res.json()); } // Enquanto não conecta, consulta o SEU backend a cada 4 s. useEffect(() => { if (!conexao || conexao.status === 'connected') return; const timer = setInterval(async () => { const res = await fetch(`/api/whatsapp/conexoes/${conexao.id}`); setConexao(await res.json()); }, 4000); return () => clearInterval(timer); }, [conexao?.id, conexao?.status]); if (!conexao) return ; if (conexao.status === 'connected') return

WhatsApp {conexao.telefone} conectado!

; return (
{conexao.qrCode ? ( QR Code para conectar o WhatsApp ) : (

Gerando o QR Code…

)}

No celular: WhatsApp → Aparelhos conectados → Conectar um aparelho.

); } ``` * HTML + JavaScript conectar.html ```html

``` ## Conectar com código de pareamento Sem câmera à mão (ex.: o celular é o mesmo aparelho que mostra a tela)? Peça um **código de pareamento** de 8 caracteres em vez do QR Code. 1. **Tenha um canal que ainda não está conectado.** Crie com [`POST /channels`](#criar-canal) (ignore o QR Code da resposta) ou use um canal `disconnected`. 2. **Peça o código** com [`POST /channels/{channel_id}/connect`](#gerar-qr-code-ou-c%C3%B3digo-de-pareamento), enviando em `phone` o número do **WhatsApp que vai ser conectado** (o do celular da empresa, com DDI e DDD — não o de um cliente). A resposta pode levar **até \~20 segundos**: use um timeout de pelo menos 30 s. ```bash curl https://platform.toptalking.com.br/v1/channels/742539b3-2c69-4acc-988f-fcd836e565a9/connect \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone": "5511955554444" }' ``` ```json { "object": "channel_connection", "channel": { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda", "phone": null, "status": "connecting", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" }, "status": "connecting", "qr_code": null, "qr_code_generated_at": null, "pairing_code": "WZYE-H1YY" } ``` 3. **Digite o código no celular:** WhatsApp → **Aparelhos conectados → Conectar um aparelho → Conectar com número de telefone** e digite `WZYE-H1YY`. 4. **Espere o `connected`.** Consulte [`GET /channels/{channel_id}/qr`](#consultar-o-qr-code) a cada 3–5 s (o `status` vira `connected`) ou assine [`channel.status_updated`](/webhooks/eventos/#channelstatus_updated). Se o código não funcionar, peça outro chamando o `/connect` de novo. ## Reconectar um WhatsApp desconectado Quando um canal fica `disconnected` (o celular saiu de “Aparelhos conectados”, trocou de aparelho, ficou muito tempo sem internet…), **não crie um canal novo**: gere um novo QR Code para o mesmo canal. Assim as conversas continuam no mesmo lugar. 1. Chame [`POST /channels/{channel_id}/connect`](#gerar-qr-code-ou-c%C3%B3digo-de-pareamento) sem corpo (ou com `{ "phone": "..." }` para receber um código de pareamento). 2. Mostre o `qr_code` e siga os passos 3 e 4 de [Conectar um WhatsApp por QR Code](#conectar-um-whatsapp-por-qr-code). Se o canal já estiver conectado, a API responde `409 invalid_state`: para trocar o número, [desconecte](#desconectar-canal) antes. ## O objeto canal ```json { "id": "d781e759-4047-4c35-bfcd-eae8c666369a", "object": "channel", "type": "whatsapp", "name": "Vendas", "phone": "5511988887777", "status": "connected", "is_primary": true, "created_at": "2026-03-02T12:10:44Z" } ``` | Campo | Tipo | Descrição | | ------------ | ---------------- | ---------------------------------------------------------------------------------------------------- | | `id` | UUID | ID do canal. Use em `channel_id` para escolher por qual número enviar. | | `object` | string | Sempre `channel`. | | `type` | string | `whatsapp` (número conectado por QR Code) ou `webchat` (Chat do site). | | `name` | string | Nome do canal no painel (ex.: “Vendas”). | | `phone` | string ou `null` | Número do WhatsApp, só dígitos com DDI. `null` no Chat do site e em canais que ainda não conectaram. | | `status` | string | `connected`, `connecting` ou `disconnected`. Veja [Status da conexão](#status-da-conex%C3%A3o). | | `is_primary` | booleano | Se é o WhatsApp principal — o usado por padrão em `POST /messages`. | | `created_at` | data | Quando o canal foi criado. | ## O objeto conexão Devolvido por [`POST /channels`](#criar-canal), [`POST /channels/{channel_id}/connect`](#gerar-qr-code-ou-c%C3%B3digo-de-pareamento) e [`GET /channels/{channel_id}/qr`](#consultar-o-qr-code). Mostra o estado do pareamento de um canal de WhatsApp. ```json { "object": "channel_connection", "channel": { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda", "phone": null, "status": "connecting", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" }, "status": "connecting", "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARQAAAEUCAYAAADqcMl5AAAAAklEQVR4AewaftIAAA...", "qr_code_generated_at": "2026-11-11T15:20:31Z", "pairing_code": null } ``` | Campo | Tipo | Descrição | | ---------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `object` | string | Sempre `channel_connection`. | | `channel` | objeto | O [objeto canal](#o-objeto-canal) atualizado. | | `status` | string | `connected` (o celular leu o código e o canal já envia mensagens), `connecting` ou `disconnected`. | | `qr_code` | string ou `null` | Imagem PNG em data URL (`data:image/png;base64,...`), pronta para o `src` de uma imagem. `null` quando o canal está conectado, quando não há pareamento em andamento, quando o QR Code ainda está sendo gerado ou quando você pediu código de pareamento. | | `qr_code_generated_at` | data ou `null` | Quando o QR Code atual foi gerado. Mudou? Troque a imagem. | | `pairing_code` | string ou `null` | Código de pareamento (8 caracteres, ex.: `WZYE-H1YY`). Só vem na resposta de `POST /channels/{channel_id}/connect` com `phone`. | ## Listar canais GET`/v1/channels`Testar Devolve todos os canais da organização — WhatsApp e Chat do site, conectados ou não. Use para descobrir o `channel_id` de cada número e para **monitorar se algum WhatsApp caiu**. Não tem parâmetros. A lista vem inteira numa página (`has_more: false`). * curl ```bash curl https://platform.toptalking.com.br/v1/channels \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/channels', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: canais } = await res.json(); const caidos = canais.filter((c) => c.type === 'whatsapp' && c.status !== 'connected'); if (caidos.length) console.warn('WhatsApp sem conexão:', caidos.map((c) => c.name)); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/channels", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() canais = res.json()["data"] caidos = [c["name"] for c in canais if c["type"] == "whatsapp" and c["status"] != "connected"] ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "d781e759-4047-4c35-bfcd-eae8c666369a", "object": "channel", "type": "whatsapp", "name": "Vendas", "phone": "5511988887777", "status": "connected", "is_primary": true, "created_at": "2026-03-02T12:10:44Z" }, { "id": "9d8adaf6-a08d-4027-b07c-5df1e89e117b", "object": "channel", "type": "whatsapp", "name": "Suporte", "phone": "5511977776666", "status": "disconnected", "is_primary": false, "created_at": "2026-05-18T09:31:02Z" }, { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda", "phone": null, "status": "connecting", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" }, { "id": "26facac8-3d9a-41b3-bfee-88851aba96ec", "object": "channel", "type": "webchat", "name": "Chat do site", "phone": null, "status": "connected", "is_primary": false, "created_at": "2026-06-20T17:45:10Z" } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## Criar canal POST`/v1/channels`Testar Cria um canal de WhatsApp e **já inicia o pareamento**: a resposta traz o primeiro QR Code. É o primeiro passo para [conectar um WhatsApp por QR Code](#conectar-um-whatsapp-por-qr-code). Depois, consulte [`GET /channels/{channel_id}/qr`](#consultar-o-qr-code) até `status` ser `connected`. Cada canal criado conta no **limite de conexões do plano** — veja [Limite de conexões do plano](#limite-de-conex%C3%B5es-do-plano). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ----------------- | --------- | ------ | ----------------- | ---------------------------------------------------------------------------------------------------- | | `name` | corpo | string | não | Nome do canal no painel (até 80 caracteres), ex.: “Pós-venda”. O corpo inteiro é opcional. | | `Idempotency-Key` | cabeçalho | string | não (recomendado) | Evita criar dois canais se a requisição for repetida. Veja [Idempotência](/comecando/idempotencia/). | * curl ```bash curl https://platform.toptalking.com.br/v1/channels \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: conexao-pos-venda-001" \ -d '{ "name": "Pós-venda" }' ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/channels', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'conexao-pos-venda-001', }, body: JSON.stringify({ name: 'Pós-venda' }), }); const conexao = await res.json(); if (!res.ok) throw new Error(`${conexao.error.code}: ${conexao.error.message}`); console.log(conexao.channel.id, conexao.qr_code ? 'QR Code pronto' : 'gerando QR Code…'); ``` * Python ```python import os import requests res = requests.post( "https://platform.toptalking.com.br/v1/channels", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": "conexao-pos-venda-001", }, json={"name": "Pós-venda"}, timeout=30, ) conexao = res.json() if not res.ok: raise RuntimeError(f"{conexao['error']['code']}: {conexao['error']['message']}") channel_id = conexao["channel"]["id"] ``` #### Resposta `201` O [objeto conexão](#o-objeto-conex%C3%A3o), com o canal em `connecting`: ```json { "object": "channel_connection", "channel": { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda", "phone": null, "status": "connecting", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" }, "status": "connecting", "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARQAAAEUCAYAAADqcMl5AAAAAklEQVR4AewaftIAAA...", "qr_code_generated_at": "2026-11-11T15:20:02Z", "pairing_code": null } ``` `qr_code` pode vir `null` se o QR Code ainda estiver sendo gerado: continue consultando `GET /channels/{channel_id}/qr`. #### Erros | HTTP | `code` | Quando acontece | | ---- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `invalid_json`, `invalid_field` | Corpo inválido, `name` com mais de 80 caracteres ou campo desconhecido. | | 403 | `read_only_key` | Chave somente leitura. | | 422 | `limit_reached` | O plano já tem o máximo de conexões de WhatsApp. | | 502 | `provider_error`, `internal_error` | O WhatsApp não respondeu ao criar a conexão. Tente de novo com a mesma `Idempotency-Key`. | | 504 | `timeout` | O lado do WhatsApp demorou demais. Tente de novo com a mesma `Idempotency-Key`: se a primeira tiver dado certo, você recebe o canal já criado. | 422 limit_reached ```json { "error": { "type": "invalid_request_error", "code": "limit_reached", "message": "Seu plano permite 3 conexões de WhatsApp. Remova uma ou faça upgrade no painel.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Buscar canal GET`/v1/channels/{channel_id}`Testar Um canal pelo ID. Útil para conferir o `status` de um número específico antes de um envio importante. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | ------------ | | `channel_id` | caminho | UUID | sim | ID do canal. | * curl ```bash curl https://platform.toptalking.com.br/v1/channels/d781e759-4047-4c35-bfcd-eae8c666369a \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const channelId = 'd781e759-4047-4c35-bfcd-eae8c666369a'; const res = await fetch(`https://platform.toptalking.com.br/v1/channels/${channelId}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const canal = await res.json(); if (canal.status !== 'connected') console.warn(`O canal ${canal.name} está ${canal.status}.`); ``` * Python ```python import os import requests channel_id = "d781e759-4047-4c35-bfcd-eae8c666369a" res = requests.get( f"https://platform.toptalking.com.br/v1/channels/{channel_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() canal = res.json() ``` #### Resposta `200` ```json { "id": "d781e759-4047-4c35-bfcd-eae8c666369a", "object": "channel", "type": "whatsapp", "name": "Vendas", "phone": "5511988887777", "status": "connected", "is_primary": true, "created_at": "2026-03-02T12:10:44Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ----------------------------------------------------------- | | 404 | `resource_not_found` | O canal não existe, foi removido ou é de outra organização. | ## Consultar o QR Code GET`/v1/channels/{channel_id}/qr`Testar Devolve o **QR Code mais recente** do pareamento em andamento e o status da conexão, para você mostrar na tela e saber quando parar. É a rota que você chama **a cada 3–5 segundos** durante a conexão. Ela **não gera** um pareamento novo (para isso, use [`POST /channels/{channel_id}/connect`](#gerar-qr-code-ou-c%C3%B3digo-de-pareamento)). Quando o celular lê o código, `status` vira `connected` e `qr_code` volta `null`. Só funciona para canais `whatsapp`. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | ------------ | | `channel_id` | caminho | UUID | sim | ID do canal. | * curl ```bash curl https://platform.toptalking.com.br/v1/channels/742539b3-2c69-4acc-988f-fcd836e565a9/qr \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const channelId = '742539b3-2c69-4acc-988f-fcd836e565a9'; const res = await fetch(`https://platform.toptalking.com.br/v1/channels/${channelId}/qr`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const conexao = await res.json(); if (conexao.status === 'connected') console.log('Conectado:', conexao.channel.phone); else if (conexao.qr_code) mostrarNaTela(conexao.qr_code); // sua função ``` * Python ```python import os import requests channel_id = "742539b3-2c69-4acc-988f-fcd836e565a9" res = requests.get( f"https://platform.toptalking.com.br/v1/channels/{channel_id}/qr", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() conexao = res.json() print(conexao["status"], "QR Code disponível" if conexao["qr_code"] else "sem QR Code") ``` #### Resposta `200` Enquanto espera a leitura: ```json { "object": "channel_connection", "channel": { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda", "phone": null, "status": "connecting", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" }, "status": "connecting", "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARQAAAEUCAYAAADqcMl5AAAAAklEQVR4AewaftIAAA...", "qr_code_generated_at": "2026-11-11T15:20:31Z", "pairing_code": null } ``` Depois que o celular leu o código, a mesma chamada devolve `status: "connected"`, `qr_code: null` e o número em `channel.phone` (veja o passo 4 de [Conectar um WhatsApp por QR Code](#conectar-um-whatsapp-por-qr-code)). | `status` | `qr_code` | O que fazer | | -------------- | ---------- | ----------------------------------------------------------------------------------- | | `connecting` | preenchido | Mostre (ou troque) a imagem e consulte de novo em 3–5 s. | | `connecting` | `null` | O QR Code está sendo gerado (ou você pediu código de pareamento). Consulte de novo. | | `connected` | `null` | Pronto. Pare de consultar. | | `disconnected` | `null` | Não há pareamento em andamento. Chame `POST /channels/{channel_id}/connect`. | #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ----------------------------------------------------------- | | 400 | `invalid_field` | O canal é o Chat do site (`webchat`), que não usa QR Code. | | 404 | `resource_not_found` | O canal não existe, foi removido ou é de outra organização. | ## Gerar QR Code ou código de pareamento POST`/v1/channels/{channel_id}/connect`Testar Inicia um **novo pareamento** num canal de WhatsApp que **não está conectado** e devolve o QR Code. Use para: * **reconectar** um canal `disconnected`; * gerar outro QR Code quando o anterior não foi lido a tempo; * conectar **sem câmera**: envie `phone` e receba um `pairing_code` para digitar no celular (veja [Conectar com código de pareamento](#conectar-com-c%C3%B3digo-de-pareamento)). O QR Code vale cerca de 20 a 40 segundos e é renovado sozinho: depois desta chamada, consulte [`GET /channels/{channel_id}/qr`](#consultar-o-qr-code) a cada poucos segundos até `status` virar `connected`. Com `phone`, a resposta pode demorar **até \~20 segundos**. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `channel_id` | caminho | UUID | sim | ID do canal de WhatsApp. | | `phone` | corpo | string | não | Número do WhatsApp **que vai ser conectado**, com DDI e DDD (ex.: `5511955554444`). Com ele, a resposta traz `pairing_code` em vez de `qr_code`. Sem corpo (ou `{}`), vem o QR Code. | * curl ```bash # QR Code curl -X POST https://platform.toptalking.com.br/v1/channels/9d8adaf6-a08d-4027-b07c-5df1e89e117b/connect \ -H "Authorization: Bearer $TOPTALKING_API_KEY" # Código de pareamento (sem câmera) curl https://platform.toptalking.com.br/v1/channels/9d8adaf6-a08d-4027-b07c-5df1e89e117b/connect \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone": "5511977776666" }' ``` * JavaScript ```js async function gerarPareamento(channelId, phone) { const res = await fetch(`https://platform.toptalking.com.br/v1/channels/${channelId}/connect`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(phone ? { phone } : {}), signal: AbortSignal.timeout(30_000), // com phone pode levar ~20 s }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return body; // body.qr_code ou body.pairing_code } const { pairing_code } = await gerarPareamento('9d8adaf6-a08d-4027-b07c-5df1e89e117b', '5511977776666'); console.log('Digite no celular:', pairing_code); // "WZYE-H1YY" ``` * Python ```python import os import requests channel_id = "9d8adaf6-a08d-4027-b07c-5df1e89e117b" res = requests.post( f"https://platform.toptalking.com.br/v1/channels/{channel_id}/connect", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={"phone": "5511977776666"}, # ou json={} para receber o QR Code timeout=30, # com phone pode levar ~20 s ) conexao = res.json() if not res.ok: raise RuntimeError(f"{conexao['error']['code']}: {conexao['error']['message']}") print("Digite no celular:", conexao["pairing_code"]) ``` #### Resposta `200` Sem `phone` (QR Code): ```json { "object": "channel_connection", "channel": { "id": "9d8adaf6-a08d-4027-b07c-5df1e89e117b", "object": "channel", "type": "whatsapp", "name": "Suporte", "phone": "5511977776666", "status": "connecting", "is_primary": false, "created_at": "2026-05-18T09:31:02Z" }, "status": "connecting", "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARQAAAEUCAYAAADqcMl5AAAAAklEQVR4AewaftIAAA...", "qr_code_generated_at": "2026-11-11T15:42:08Z", "pairing_code": null } ``` Com `phone` (código de pareamento): ```json { "object": "channel_connection", "channel": { "id": "9d8adaf6-a08d-4027-b07c-5df1e89e117b", "object": "channel", "type": "whatsapp", "name": "Suporte", "phone": "5511977776666", "status": "connecting", "is_primary": false, "created_at": "2026-05-18T09:31:02Z" }, "status": "connecting", "qr_code": null, "qr_code_generated_at": null, "pairing_code": "WZYE-H1YY" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------------------------------------------------------------------------------- | | 400 | `invalid_field` | O canal é o Chat do site (`webchat`), que não usa QR Code, ou há campo desconhecido no corpo. | | 400 | `invalid_phone` | `phone` não parece um número válido. | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O canal não existe ou é de outra organização. | | 409 | `invalid_state` | O canal **já está conectado**. Para trocar o número, desconecte antes. | | 502 | `provider_error` | O WhatsApp não devolveu o QR Code ou o código de pareamento. Confira o número e tente de novo. | | 504 | `timeout` | O lado do WhatsApp demorou demais para responder. Tente de novo em instantes. | 409 invalid_state ```json { "error": { "type": "conflict_error", "code": "invalid_state", "message": "Este WhatsApp já está conectado. Para trocar o número, desconecte antes.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Renomear canal PATCH`/v1/channels/{channel_id}`Testar Muda o nome do canal no painel (ex.: de “WhatsApp” para “Suporte”). Não afeta a conexão nem as conversas. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ------ | ----------- | ------------------------------ | | `channel_id` | caminho | UUID | sim | ID do canal. | | `name` | corpo | string | sim | Novo nome (até 80 caracteres). | * curl ```bash curl -X PATCH https://platform.toptalking.com.br/v1/channels/742539b3-2c69-4acc-988f-fcd836e565a9 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Pós-venda e trocas" }' ``` * JavaScript ```js const channelId = '742539b3-2c69-4acc-988f-fcd836e565a9'; const res = await fetch(`https://platform.toptalking.com.br/v1/channels/${channelId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Pós-venda e trocas' }), }); const canal = await res.json(); ``` * Python ```python import os import requests channel_id = "742539b3-2c69-4acc-988f-fcd836e565a9" res = requests.patch( f"https://platform.toptalking.com.br/v1/channels/{channel_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={"name": "Pós-venda e trocas"}, timeout=30, ) res.raise_for_status() canal = res.json() ``` #### Resposta `200` ```json { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda e trocas", "phone": "5511955554444", "status": "connected", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | ------------------------------- | ----------------------------------------------------------------------- | | 400 | `missing_field` | Faltou `name`. | | 400 | `invalid_json`, `invalid_field` | Corpo inválido, `name` com mais de 80 caracteres ou campo desconhecido. | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O canal não existe ou é de outra organização. | ## Desconectar canal POST`/v1/channels/{channel_id}/disconnect`Testar Encerra a sessão do WhatsApp neste canal — o mesmo que **Desconectar** no painel (o aparelho some de “Aparelhos conectados” no celular). O canal continua existindo, com `status: "disconnected"`, e **as conversas continuam no painel**. Para usar o número de novo (ou conectar outro número no mesmo canal), chame [`POST /channels/{channel_id}/connect`](#gerar-qr-code-ou-c%C3%B3digo-de-pareamento). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | ------------------------ | | `channel_id` | caminho | UUID | sim | ID do canal de WhatsApp. | * curl ```bash curl -X POST https://platform.toptalking.com.br/v1/channels/742539b3-2c69-4acc-988f-fcd836e565a9/disconnect \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const channelId = '742539b3-2c69-4acc-988f-fcd836e565a9'; const res = await fetch( `https://platform.toptalking.com.br/v1/channels/${channelId}/disconnect`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` } }, ); const canal = await res.json(); // canal.status === "disconnected" ``` * Python ```python import os import requests channel_id = "742539b3-2c69-4acc-988f-fcd836e565a9" res = requests.post( f"https://platform.toptalking.com.br/v1/channels/{channel_id}/disconnect", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() canal = res.json() ``` #### Resposta `200` O [objeto canal](#o-objeto-canal), já desconectado: ```json { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda e trocas", "phone": "5511955554444", "status": "disconnected", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | --------------------------------------------- | | 400 | `invalid_field` | O canal é o Chat do site (`webchat`). | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O canal não existe ou é de outra organização. | ## Remover canal DELETE`/v1/channels/{channel_id}`Testar Desconecta o WhatsApp e **remove o canal da organização**, liberando uma conexão do plano. As conversas que já existiam continuam no painel. Não dá para desfazer: para usar o número de novo, [crie outro canal](#criar-canal). O Chat do site (`webchat`) só pode ser removido no painel. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | ------------------------ | | `channel_id` | caminho | UUID | sim | ID do canal de WhatsApp. | * curl ```bash curl -X DELETE https://platform.toptalking.com.br/v1/channels/742539b3-2c69-4acc-988f-fcd836e565a9 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const channelId = '742539b3-2c69-4acc-988f-fcd836e565a9'; const res = await fetch(`https://platform.toptalking.com.br/v1/channels/${channelId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { deleted } = await res.json(); // true ``` * Python ```python import os import requests channel_id = "742539b3-2c69-4acc-988f-fcd836e565a9" res = requests.delete( f"https://platform.toptalking.com.br/v1/channels/{channel_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() ``` #### Resposta `200` ```json { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "deleted": true } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | -------------------------------------------------------------- | | 400 | `invalid_field` | O canal é o Chat do site (`webchat`): remova pelo painel. | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O canal não existe, já foi removido ou é de outra organização. | 400 invalid_field ```json { "error": { "type": "invalid_request_error", "code": "invalid_field", "message": "O Chat do site é gerenciado no painel (Configurações → Conexões).", "param": "channel_id", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Limite de conexões do plano Cada plano da TopTalking permite um número de **conexões de WhatsApp** (canais `whatsapp`). Canais `disconnected` também contam — só [remover](#remover-canal) libera a vaga. O Chat do site não conta. Ao passar do limite, `POST /channels` responde `422 limit_reached`: ```json { "error": { "type": "invalid_request_error", "code": "limit_reached", "message": "Seu plano permite 3 conexões de WhatsApp. Remova uma ou faça upgrade no painel.", "request_id": "req_6f1d2c3b4a5e" } } ``` O que fazer: reconecte um canal existente com [`POST /channels/{channel_id}/connect`](#gerar-qr-code-ou-c%C3%B3digo-de-pareamento) em vez de criar outro, remova um canal que não usa mais ou faça upgrade do plano no painel. [Enviar mensagens](/referencia/mensagens/#enviar-mensagem)POST /messages, com channel_id para escolher o número. [Evento channel.status_updated](/webhooks/eventos/#channelstatus_updated)Seja avisado quando um WhatsApp conecta ou cai. # Contatos > Contatos (conversas) na API da TopTalking - listar e filtrar (GET /contacts), criar ou atualizar pelo telefone (POST /contacts), atualizar, mover no Kanban, atribuir responsável, fixar e escolher o agente de IA (PATCH /contacts/{contact_id}), marcar como lida e ler o histórico. Na TopTalking, um **contato é uma conversa** da caixa de entrada: um por número de telefone e canal. O contato guarda os dados da pessoa (nome, telefone, e-mail) e o estado do atendimento (etapa do Kanban, responsável, agente de IA, fixada, finalizada, arquivada). [Grupos](/referencia/grupos/) também são conversas: aparecem aqui com `is_group: true`, e o `id` do grupo funciona em todos os endpoints desta página. ## O objeto contato ```json { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 2, "last_message_at": "2026-11-11T14:12:40Z", "last_message_preview": "Vocês entregam no sábado?", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:12:40Z" } ``` | Campo | Tipo | Descrição | | ---------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- | | `id` | UUID | ID do contato (da conversa). | | `object` | string | Sempre `contact`. | | `name` | string ou `null` | Nome do contato (ou do grupo). | | `phone` | string ou `null` | Telefone só com dígitos, com DDI (ex.: `5511999998888`). `null` em grupos e em conversas do Chat do site sem telefone. | | `email` | string ou `null` | E-mail. | | `channel_id` | UUID ou `null` | [Canal](/referencia/canais/) da conversa. | | `channel_type` | string | `whatsapp` ou `webchat` (Chat do site). | | `is_group` | booleano | Grupo de WhatsApp. Grupos não têm IA, mas recebem mensagens pela API (veja [Grupos](/referencia/grupos/)). | | `stage` | objeto ou `null` | Etapa atual no Kanban (`id`, `name`, `is_final`), ou `null` se não está em nenhuma. | | `ai_enabled` | booleano | Se a IA responde esta conversa. | | `ai_agent_id` | UUID ou `null` | [Agente de IA](/referencia/agentes-ia/) da conversa. `null` = o agente padrão. | | `assigned_to` | UUID ou `null` | [Membro](/referencia/equipe/) responsável pela conversa. `null` = sem responsável. | | `pinned` | booleano | Conversa fixada no topo da caixa de entrada. | | `unread_count` | inteiro | Mensagens não lidas no painel. | | `last_message_at` | data ou `null` | Data da última mensagem. | | `last_message_preview` | string ou `null` | Prévia da última mensagem. | | `resolved_at` | data ou `null` | Quando a conversa foi finalizada (`null` = em aberto). | | `archived_at` | data ou `null` | Quando a conversa foi arquivada (`null` = ativa). | | `source` | string | Origem do contato: `whatsapp`, `webchat`, `api`, `agenda`, `lead`… | | `created_at` | data | Criação. | | `updated_at` | data | Última alteração. | ## Listar contatos GET`/v1/contacts`Testar Lista as conversas ordenadas pela **última mensagem** (mais recentes primeiro). Use para buscar um contato, montar a sua caixa de entrada, ler um quadro do Kanban ou a fila de um atendente. Todos os filtros são opcionais e podem ser combinados. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------- | ----- | -------- | ----------- | ----------------------------------------------------------------------------------- | | `search` | query | string | não | Busca parcial por nome ou telefone (até 100 caracteres). | | `phone` | query | string | não | Telefone exato. Aceita formatado (`+55 11 99999-8888`); é normalizado para dígitos. | | `stage_id` | query | UUID | não | Só contatos nesta etapa do Kanban. | | `channel_id` | query | UUID | não | Só contatos deste canal. | | `archived` | query | booleano | não | `true` só arquivados, `false` só ativos. Sem o parâmetro, todos. | | `is_group` | query | booleano | não | `false` só conversas individuais, `true` só grupos. Sem o parâmetro, todos. | | `assigned_to` | query | string | não | ID do membro responsável, ou `none` para conversas **sem responsável**. | | `limit` | query | inteiro | não | Itens por página, 1 a 100 (padrão 20). | | `cursor` | query | string | não | `next_cursor` da página anterior. Veja [Paginação](/comecando/paginacao/). | * curl ```bash # Conversas individuais ativas na etapa "Negociação" curl "https://platform.toptalking.com.br/v1/contacts?stage_id=9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11&archived=false&is_group=false&limit=50" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" # Procurar um contato pelo telefone curl "https://platform.toptalking.com.br/v1/contacts?phone=5511999998888" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" # Conversas sem responsável curl "https://platform.toptalking.com.br/v1/contacts?assigned_to=none&archived=false" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const url = new URL('https://platform.toptalking.com.br/v1/contacts'); url.searchParams.set('search', 'maria'); url.searchParams.set('archived', 'false'); url.searchParams.set('is_group', 'false'); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: contatos, has_more, next_cursor } = await res.json(); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/contacts", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"search": "maria", "archived": "false", "is_group": "false"}, timeout=30, ) res.raise_for_status() pagina = res.json() contatos = pagina["data"] ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 2, "last_message_at": "2026-11-11T14:12:40Z", "last_message_preview": "Vocês entregam no sábado?", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:12:40Z" } ], "has_more": true, "next_cursor": "eyJ0IjoiMjAyNi0xMS0xMVQxNDoxMjo0MFoifQ" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | ------------------------------------- | -------------------------------------------------------------------- | | 401 | `missing_api_key`, `invalid_api_key`… | Problema com a chave. Veja [Autenticação](/comecando/autenticacao/). | ## Criar ou atualizar contato POST`/v1/contacts`Testar Cria o contato pelo telefone. **Se já existir um contato com esse telefone na organização, os campos enviados são atualizados** e a resposta é `200` em vez de `201` — então dá para chamar sem verificar antes (“upsert”). Ideal para formulários de lead e sincronização com o seu CRM. O contato criado aparece na caixa de entrada do painel. Para já colocá-lo no funil, envie `stage_id`. 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. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ----------------- | --------- | ------ | ----------- | ------------------------------------------------------------------------------------------------ | | `phone` | corpo | string | sim | Telefone com DDI e DDD. Formatação é ignorada; números brasileiros sem `55` recebem o `55`. | | `name` | corpo | string | não | Nome (até 120 caracteres). | | `email` | corpo | string | não | E-mail. | | `stage_id` | corpo | UUID | não | Já coloca o card nesta etapa do Kanban. Veja [`GET /stages`](/referencia/kanban/#listar-etapas). | | `channel_id` | corpo | UUID | não | Canal de WhatsApp da conversa. Padrão: o WhatsApp principal. | | `Idempotency-Key` | cabeçalho | string | não | Para retentativas seguras. Veja [Idempotência](/comecando/idempotencia/). | * curl ```bash curl https://platform.toptalking.com.br/v1/contacts \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: lead-5511999998888" \ -d '{ "phone": "+55 11 99999-8888", "name": "Maria Souza", "email": "maria@exemplo.com", "stage_id": "e63d8231-3992-457e-a8b0-d1be2fa591d4" }' ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/contacts', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'lead-5511999998888', }, body: JSON.stringify({ phone: '+55 11 99999-8888', name: 'Maria Souza', email: 'maria@exemplo.com', stage_id: 'e63d8231-3992-457e-a8b0-d1be2fa591d4', // "Novo lead" }), }); const contato = await res.json(); console.log(res.status === 201 ? 'Contato criado' : 'Contato atualizado', contato.id); ``` * Python ```python import os import requests res = requests.post( "https://platform.toptalking.com.br/v1/contacts", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": "lead-5511999998888", }, json={ "phone": "+55 11 99999-8888", "name": "Maria Souza", "email": "maria@exemplo.com", "stage_id": "e63d8231-3992-457e-a8b0-d1be2fa591d4", # "Novo lead" }, timeout=30, ) contato = res.json() print("Contato criado" if res.status_code == 201 else "Contato atualizado", contato["id"]) ``` #### Resposta `201` (criado) ou `200` (já existia) ```json { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "e63d8231-3992-457e-a8b0-d1be2fa591d4", "name": "Novo lead", "is_final": false }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": null, "pinned": false, "unread_count": 0, "last_message_at": null, "last_message_preview": null, "resolved_at": null, "archived_at": null, "source": "api", "created_at": "2026-11-11T14:20:05Z", "updated_at": "2026-11-11T14:20:05Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | ---------------------- | ------------------------------------------------------------------------------------------------------ | | 400 | `missing_field` | Faltou `phone`. | | 400 | `invalid_phone` | Telefone inválido. | | 400 | `invalid_field` | Algum campo inválido (ex.: `email`, `stage_id` que não é etapa da organização ou `name` longo demais). | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | `channel_id` não encontrado. | | 409 | `idempotency_conflict` | `Idempotency-Key` reutilizada com outro corpo. | 400 invalid_phone ```json { "error": { "type": "invalid_request_error", "code": "invalid_phone", "message": "Telefone inválido. Envie com DDI e DDD, ex.: 5511999998888.", "param": "phone", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Buscar contato GET`/v1/contacts/{contact_id}`Testar Um contato pelo ID — por exemplo, o `contact_id` que chegou num webhook ou numa mensagem. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | ---------------------------- | | `contact_id` | caminho | UUID | sim | ID do contato (ou do grupo). | * curl ```bash curl https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const contactId = '3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60'; const res = await fetch(`https://platform.toptalking.com.br/v1/contacts/${contactId}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const contato = await res.json(); ``` * Python ```python import os import requests contact_id = "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60" res = requests.get( f"https://platform.toptalking.com.br/v1/contacts/{contact_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() contato = res.json() ``` #### Resposta `200` O [objeto contato](#o-objeto-contato): ```json { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 2, "last_message_at": "2026-11-11T14:12:40Z", "last_message_preview": "Vocês entregam no sábado?", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:12:40Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------------------------------------------------------------- | | 404 | `resource_not_found` | O contato não existe (ex.: foi apagado no painel) ou é de outra organização. | ## Atualizar contato (e mover no Kanban) PATCH`/v1/contacts/{contact_id}`Testar Envie **só os campos que quer mudar**. É com este endpoint que você: * **move o card no Kanban** (`stage_id`); * **atribui a conversa** a um atendente (`assigned_to`) — veja [Equipe](/referencia/equipe/#atribuir-uma-conversa); * **liga ou desliga a IA** naquela conversa (`ai_enabled`) ou **troca o agente** (`ai_agent_id`) — veja [Agentes de IA](/referencia/agentes-ia/#agente-e-ia-por-conversa); * **fixa** a conversa no topo (`pinned`); * **finaliza ou reabre** a conversa (`resolved`); * **arquiva ou desarquiva** (`archived`); * corrige nome e e-mail. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------- | ------- | ---------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `contact_id` | caminho | UUID | sim | ID do contato (ou do grupo). | | `name` | corpo | string | não | Nome (até 120 caracteres). | | `email` | corpo | string ou `null` | não | E-mail. `null` apaga. | | `stage_id` | corpo | UUID ou `null` | não | Move o card para esta etapa. `null` tira o card de qualquer etapa. Mover para a etapa final (`is_final: true`) **também finaliza a conversa**, igual ao painel. | | `assigned_to` | corpo | UUID ou `null` | não | Membro responsável (veja [`GET /members`](/referencia/equipe/#listar-membros)). `null` libera a conversa. | | `ai_enabled` | corpo | booleano | não | `true` liga e `false` desliga a IA nesta conversa. | | `ai_agent_id` | corpo | UUID ou `null` | não | Agente de IA desta conversa (veja [`GET /ai-agents`](/referencia/agentes-ia/#listar-agentes-de-ia)). `null` volta para o agente padrão. | | `pinned` | corpo | booleano | não | `true` fixa a conversa no topo da caixa de entrada; `false` desafixa. | | `resolved` | corpo | booleano | não | `true` finaliza a conversa; `false` reabre. | | `archived` | corpo | booleano | não | `true` arquiva; `false` desarquiva. | * curl ```bash # Mover para a etapa "Negociação" curl -X PATCH https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "stage_id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11" }' # Um humano vai assumir: desligar a IA, atribuir à Carla e fixar curl -X PATCH https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ai_enabled": false, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": true }' ``` * JavaScript ```js async function atualizarContato(contactId, campos) { const res = await fetch(`https://platform.toptalking.com.br/v1/contacts/${contactId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(campos), }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return body; } // Mover no Kanban await atualizarContato('3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60', { stage_id: '9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11', }); // Passar para o agente de IA "Vendas" await atualizarContato('3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60', { ai_agent_id: '52da47ff-662a-4292-9961-27a95fc5ddd8', }); // Finalizar a conversa await atualizarContato('3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60', { resolved: true }); ``` * Python ```python import os import requests def atualizar_contato(contact_id: str, campos: dict) -> dict: res = requests.patch( f"https://platform.toptalking.com.br/v1/contacts/{contact_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json=campos, timeout=30, ) body = res.json() if not res.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}") return body # Um humano vai assumir: desligar a IA, atribuir à Carla e fixar atualizar_contato( "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", {"ai_enabled": False, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": True}, ) ``` #### Resposta `200` O contato atualizado (aqui, depois do segundo exemplo em curl): ```json { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "ai_enabled": false, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": true, "unread_count": 2, "last_message_at": "2026-11-11T14:12:40Z", "last_message_preview": "Vocês entregam no sábado?", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:35:21Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `missing_field` | O corpo não tem nenhum campo para atualizar. | | 400 | `invalid_json`, `invalid_field` | Corpo inválido ou campo com valor inválido: `stage_id` que não é etapa da organização, `assigned_to` que não é membro, `ai_agent_id` que não é agente da organização… (`param` diz qual). | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O contato não existe. | 400 invalid_field ```json { "error": { "type": "invalid_request_error", "code": "invalid_field", "message": "Este usuário não é membro da organização. Veja GET /v1/members.", "param": "assigned_to", "request_id": "req_6f1d2c3b4a5e" } } ``` Cada alteração de nome, e-mail, etapa, responsável, IA, arquivamento ou finalização dispara o webhook [`contact.updated`](/webhooks/eventos/#contactupdated) e, se a etapa mudou, [`contact.stage_changed`](/webhooks/eventos/#contactstage_changed). ## Marcar conversa como lida POST`/v1/contacts/{contact_id}/read`Testar Zera o contador de não lidas (`unread_count`) da conversa no painel. Use quando o seu sistema (ou a sua IA) já tratou as mensagens novas, para a equipe não abrir a conversa à toa. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | ---------------------------- | | `contact_id` | caminho | UUID | sim | ID do contato (ou do grupo). | * curl ```bash curl -X POST https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60/read \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const contactId = '3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60'; const res = await fetch(`https://platform.toptalking.com.br/v1/contacts/${contactId}/read`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const contato = await res.json(); // contato.unread_count === 0 ``` * Python ```python import os import requests contact_id = "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60" res = requests.post( f"https://platform.toptalking.com.br/v1/contacts/{contact_id}/read", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() ``` #### Resposta `200` O contato, com `unread_count: 0`: ```json { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 0, "last_message_at": "2026-11-11T14:12:40Z", "last_message_preview": "Vocês entregam no sábado?", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:13:02Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------- | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O contato não existe. | ## Histórico da conversa GET`/v1/contacts/{contact_id}/messages`Testar Mensagens da conversa, **das mais recentes para as mais antigas**, com [paginação](/comecando/paginacao/). Use para mostrar a conversa no seu sistema ou dar contexto a uma IA. Funciona também para grupos (com o `id` do grupo). Mensagens apagadas com `scope=me` não aparecem. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ------- | ----------- | -------------------------------------- | | `contact_id` | caminho | UUID | sim | ID do contato (ou do grupo). | | `limit` | query | inteiro | não | Itens por página, 1 a 100 (padrão 20). | | `cursor` | query | string | não | `next_cursor` da página anterior. | * curl ```bash curl "https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60/messages?limit=50" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const contactId = '3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60'; const res = await fetch( `https://platform.toptalking.com.br/v1/contacts/${contactId}/messages?limit=50`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` } }, ); const { data: mensagens } = await res.json(); // Para exibir em ordem cronológica: const conversa = [...mensagens].reverse(); ``` * Python ```python import os import requests contact_id = "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60" res = requests.get( f"https://platform.toptalking.com.br/v1/contacts/{contact_id}/messages", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"limit": 50}, timeout=30, ) res.raise_for_status() conversa = list(reversed(res.json()["data"])) # ordem cronológica ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "inbound", "type": "text", "text": "Vocês entregam no sábado?", "media": null, "status": "delivered", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:12:40Z", "edited_at": null }, { "id": "a9d91e32-912f-4866-b018-e7c4cfe7f45b", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Oi, Maria! Recebemos o seu pedido #1234 e ele já está sendo separado. 😊", "media": null, "status": "read", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:05:02Z", "edited_at": null } ], "has_more": true, "next_cursor": "eyJ0IjoiMjAyNi0xMS0xMVQxNDowNTowMloifQ" } ``` O formato de cada item é o [objeto mensagem](/referencia/mensagens/#o-objeto-mensagem). #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | --------------------- | | 404 | `resource_not_found` | O contato não existe. | # Mensagens > Mensagens de WhatsApp pela API da TopTalking - enviar texto, imagem, vídeo, áudio, nota de voz ou documento (POST /messages), também para grupos; acompanhar o status de entrega; editar (PATCH), apagar (DELETE) e reagir a mensagens. Tudo o que você faz com mensagens pela API aparece no chat do painel como se a equipe tivesse feito: o envio, a edição, a reação e a mensagem apagada. | Ação | Endpoint | | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | Enviar texto e/ou mídia (para contatos e [grupos](/referencia/grupos/)) | [`POST /messages`](#enviar-mensagem) | | Consultar uma mensagem e o status de entrega | [`GET /messages/{message_id}`](#buscar-mensagem) | | Editar o texto de uma mensagem enviada (até 15 min) | [`PATCH /messages/{message_id}`](#editar-mensagem) | | Apagar para todos (até 48 h) ou só no painel | [`DELETE /messages/{message_id}`](#apagar-mensagem) | | Reagir com um emoji | [`POST /messages/{message_id}/reaction`](#reagir-a-uma-mensagem) | | Ler o histórico de uma conversa | [`GET /contacts/{contact_id}/messages`](/referencia/contatos/#hist%C3%B3rico-da-conversa) | ## O objeto mensagem ```json { "id": "8d66a526-230c-481a-86e5-be09b6bbb952", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "document", "text": "Segue o boleto.", "media": { "url": "https://exemplo.com/boleto-1234.pdf", "mime_type": "application/pdf", "filename": "boleto-1234.pdf" }, "status": "delivered", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:40:31Z", "edited_at": null } ``` | Campo | Tipo | Descrição | | ------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `id` | UUID | ID da mensagem. | | `object` | string | Sempre `message`. | | `contact_id` | UUID ou `null` | Contato (conversa) da mensagem. Em grupos, é o `id` do grupo. | | `channel_id` | UUID ou `null` | Canal por onde a mensagem passou. | | `direction` | string | `inbound` (o contato mandou) ou `outbound` (a empresa mandou: equipe, IA, campanha, Agenda ou API). | | `type` | string | Valores comuns: `text`, `image`, `video`, `audio`, `document` e `sticker`. Trate valores desconhecidos como mídia genérica. | | `text` | string ou `null` | Texto ou legenda. Mídia sem legenda pode trazer um rótulo como “📷 Foto”. Mensagem apagada para todos vira “Mensagem apagada”. | | `media` | objeto ou `null` | Arquivo anexado: `url`, `mime_type` e `filename`. | | `status` | string | `queued`, `sent`, `delivered`, `read` ou `failed`. | | `error` | string ou `null` | Motivo da falha quando `status` é `failed`. | | `reply_to_id` | UUID ou `null` | Mensagem citada (quando é uma resposta). | | `reaction` | string ou `null` | Reação enviada por nós a esta mensagem (ex.: `👍`). Veja [Reagir a uma mensagem](#reagir-a-uma-mensagem). | | `sender_name` | string ou `null` | Em grupos, quem mandou. | | `transcript` | string ou `null` | Transcrição de áudio, quando houver. | | `created_at` | data | Quando a mensagem foi criada. | | `edited_at` | data ou `null` | Quando foi editada, se foi. | ### Status de entrega As mensagens enviadas (`outbound`) passam por: ```text queued → sent → delivered → read ↘ failed ``` | Status | Significado | | ----------- | -------------------------------------------------------------- | | `queued` | Na fila para sair. | | `sent` | Saiu da TopTalking para o WhatsApp. | | `delivered` | Chegou no celular do contato. | | `read` | O contato leu (se ele não desativou a confirmação de leitura). | | `failed` | Não foi possível entregar; veja `error`. | Para acompanhar sem ficar consultando, use o webhook [`message.status_updated`](/webhooks/eventos/#messagestatus_updated). ## Enviar mensagem POST`/v1/messages`Testar Envia texto e/ou mídia pelo WhatsApp (ou responde no Chat do site, se a conversa for de lá). A mensagem aparece no chat do painel como qualquer outra. É o endpoint para avisos transacionais (pedido, boleto, lembrete) e para responder conversas. **Destino:** informe `to` (telefone) **ou** `contact_id`. Se o telefone ainda não for contato, **o contato é criado** e a conversa aparece na caixa de entrada do painel. Para **grupos**, use o `id` do grupo em `contact_id` (veja [Grupos](/referencia/grupos/#enviar-mensagem-para-o-grupo)). **Canal usado**, nesta ordem: 1. o `channel_id` enviado, se houver; 2. o canal da conversa, se estiver conectado; 3. o WhatsApp principal conectado. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ----------------- | --------- | ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `to` | corpo | string | `to` ou `contact_id` | Telefone com DDI e DDD. Formatação é ignorada; números brasileiros sem `55` recebem o `55`. | | `contact_id` | corpo | UUID | `to` ou `contact_id` | Conversa de destino. Também aceita o `id` de um grupo (veja [`GET /groups`](/referencia/grupos/#listar-grupos)). | | `text` | corpo | string | `text` e/ou `media` | Até 4096 caracteres. Aceita a formatação do WhatsApp: `*negrito*`, `_itálico_`, `~tachado~`. Com `media`, vira a legenda. | | `media` | corpo | objeto | `text` e/ou `media` | Arquivo a enviar (veja abaixo). | | `channel_id` | corpo | UUID | não | Força o envio por este canal. | | `reply_to_id` | corpo | UUID | não | Responde (cita) esta mensagem da conversa. | | `contact_name` | corpo | string | não | Nome usado se o contato ainda não existir. | | `Idempotency-Key` | cabeçalho | string | não (recomendado) | Impede que uma retentativa mande a mensagem duas vezes. Veja [Idempotência](/comecando/idempotencia/). | **Objeto `media`:** | Campo | Tipo | Obrigatório | Descrição | | ------------ | -------- | ----------- | --------------------------------------------------------------------------- | | `url` | string | sim | URL **pública HTTPS** do arquivo (acessível sem login). | | `type` | string | sim | `image`, `video`, `audio` ou `document`. | | `filename` | string | não | Nome exibido (documentos). Padrão: o nome na URL. | | `mime_type` | string | não | Tipo do arquivo (ex.: `application/pdf`). Padrão: deduzido pela extensão. | | `voice_note` | booleano | não | Com `type: "audio"`, envia como nota de voz (sem legenda). Padrão: `false`. | Não use para envios em massa Este endpoint usa o WhatsApp conectado por QR Code: disparos para muitos contatos podem fazer o WhatsApp banir o número. Para promoções e avisos em massa, use [Campanhas](/referencia/campanhas/) (WhatsApp Oficial). Leia [Boas práticas no WhatsApp](/comecando/boas-praticas-whatsapp/). * curl ```bash # Texto para um telefone curl https://platform.toptalking.com.br/v1/messages \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pedido-1234-saiu" \ -d '{ "to": "5511999998888", "text": "Olá, Maria! Seu pedido *#1234* saiu para entrega." }' # Documento com legenda para um contato existente curl https://platform.toptalking.com.br/v1/messages \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: boleto-1234" \ -d '{ "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" } }' ``` * JavaScript ```js async function enviarMensagem(corpo, idempotencyKey) { const res = await fetch('https://platform.toptalking.com.br/v1/messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey, }, body: JSON.stringify(corpo), }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return body; } // Texto await enviarMensagem( { to: '5511999998888', text: 'Olá, Maria! Seu pedido *#1234* saiu para entrega.' }, 'pedido-1234-saiu', ); // Imagem com legenda await enviarMensagem( { to: '5511999998888', text: 'Olha como ficou o seu pedido 😍', media: { url: 'https://exemplo.com/fotos/pedido-1234.jpg', type: 'image' }, }, 'pedido-1234-foto', ); // Nota de voz await enviarMensagem( { contact_id: '3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60', media: { url: 'https://exemplo.com/audios/boas-vindas.ogg', type: 'audio', voice_note: true }, }, 'boas-vindas-audio-3f1c2b9a', ); // Responder citando uma mensagem recebida await enviarMensagem( { contact_id: '3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60', reply_to_id: 'b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba', text: 'Entregamos sim! Sábado das 8h às 14h.', }, 'resposta-b7fa4c19', ); ``` * Python ```python import os import requests def enviar_mensagem(corpo: dict, idempotency_key: str) -> dict: res = requests.post( "https://platform.toptalking.com.br/v1/messages", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": idempotency_key, }, json=corpo, timeout=30, ) body = res.json() if not res.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}") return body # Texto enviar_mensagem( {"to": "5511999998888", "text": "Olá, Maria! Seu pedido *#1234* saiu para entrega."}, "pedido-1234-saiu", ) # Documento com legenda enviar_mensagem( { "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", }, }, "boleto-1234", ) ``` #### Resposta `201` A mensagem criada: ```json { "id": "03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Olá, Maria! Seu pedido *#1234* saiu para entrega.", "media": null, "status": "sent", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:30:12Z", "edited_at": null } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `missing_field` | Faltou `to`/`contact_id` ou `text`/`media`. | | 400 | `invalid_phone` | Telefone em `to` inválido. | | 400 | `invalid_field` | Campo inválido: texto acima de 4096 caracteres, `media.url` sem HTTPS, `media.type` desconhecido… | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | `contact_id`, `channel_id` ou `reply_to_id` não encontrado. | | 409 | `idempotency_conflict` | `Idempotency-Key` reutilizada com outro corpo. | | 422 | `channel_unavailable` | Nenhum WhatsApp conectado (ou o canal escolhido está desconectado). [Reconecte o canal](/referencia/canais/#reconectar-um-whatsapp-desconectado). | | 502 | `provider_error` | O WhatsApp recusou o envio. A mensagem fica registrada com status `failed`. | 422 channel_unavailable ```json { "error": { "type": "channel_error", "code": "channel_unavailable", "message": "WhatsApp desconectado. Reconecte no painel.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Buscar mensagem GET`/v1/messages/{message_id}`Testar Busca uma mensagem pelo ID. Use para conferir o status de entrega (`sent` → `delivered` → `read`) de um envio importante. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | --------------- | | `message_id` | caminho | UUID | sim | ID da mensagem. | * curl ```bash curl https://platform.toptalking.com.br/v1/messages/03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const messageId = '03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e'; const res = await fetch(`https://platform.toptalking.com.br/v1/messages/${messageId}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const mensagem = await res.json(); console.log(mensagem.status); // "read" ``` * Python ```python import os import requests message_id = "03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e" res = requests.get( f"https://platform.toptalking.com.br/v1/messages/{message_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() print(res.json()["status"]) ``` #### Resposta `200` ```json { "id": "03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Olá, Maria! Seu pedido *#1234* saiu para entrega.", "media": null, "status": "read", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:30:12Z", "edited_at": null } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ------------------------------------------------ | | 404 | `resource_not_found` | A mensagem não existe ou é de outra organização. | ## Editar mensagem PATCH`/v1/messages/{message_id}`Testar Corrige o texto de uma mensagem de texto **enviada pela empresa** (`outbound`) em até **15 minutos** depois do envio — o mesmo limite do WhatsApp. O contato vê a mensagem marcada como editada, e o texto novo aparece no painel. Só mensagens de texto: mídias (inclusive legendas) não podem ser editadas. Mensagens recebidas (`inbound`) também não. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ------ | ----------- | --------------------------------- | | `message_id` | caminho | UUID | sim | ID da mensagem enviada. | | `text` | corpo | string | sim | Novo texto (até 4096 caracteres). | * curl ```bash curl -X PATCH https://platform.toptalking.com.br/v1/messages/03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Olá, Maria! Seu pedido *#1234* sai amanhã cedo." }' ``` * JavaScript ```js const messageId = '03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e'; const res = await fetch(`https://platform.toptalking.com.br/v1/messages/${messageId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ text: 'Olá, Maria! Seu pedido *#1234* sai amanhã cedo.' }), }); const body = await res.json(); if (res.status === 409) console.log('Não dá mais para editar:', body.error.message); ``` * Python ```python import os import requests message_id = "03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e" res = requests.patch( f"https://platform.toptalking.com.br/v1/messages/{message_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={"text": "Olá, Maria! Seu pedido *#1234* sai amanhã cedo."}, timeout=30, ) mensagem = res.json() ``` #### Resposta `200` A mensagem com o texto novo e `edited_at` preenchido: ```json { "id": "03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Olá, Maria! Seu pedido *#1234* sai amanhã cedo.", "media": null, "status": "delivered", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:30:12Z", "edited_at": "2026-11-11T14:38:05Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | --------------------- | ---------------------------------------------------------------------------------- | | 400 | `missing_field` | Faltou `text`. | | 400 | `invalid_field` | A mensagem é de mídia (não pode ser editada) ou o texto passou de 4096 caracteres. | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | A mensagem não existe. | | 409 | `invalid_state` | A mensagem foi recebida (`inbound`) ou já passaram 15 minutos do envio. | | 422 | `channel_unavailable` | O WhatsApp da conversa está desconectado. | | 502 | `provider_error` | O WhatsApp recusou a edição. | 409 invalid_state ```json { "error": { "type": "conflict_error", "code": "invalid_state", "message": "Só é possível editar mensagens enviadas há no máximo 15 minutos.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Apagar mensagem DELETE`/v1/messages/{message_id}`Testar Apaga uma mensagem de um destes dois jeitos: | `scope` | O que acontece | Quando dá | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | `everyone` (padrão) | **Apaga para todos** no WhatsApp. No painel e na API, o texto vira “Mensagem apagada”. | Só mensagens **enviadas** (`outbound`), em até **48 horas**. | | `me` | **Só esconde** a mensagem: ela some do painel e de [`GET /contacts/{contact_id}/messages`](/referencia/contatos/#hist%C3%B3rico-da-conversa). O contato continua vendo. | Qualquer mensagem, a qualquer momento. | Mesmo escondida com `scope=me`, a mensagem continua acessível por [`GET /messages/{message_id}`](#buscar-mensagem). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ------ | ----------- | ---------------------------- | | `message_id` | caminho | UUID | sim | ID da mensagem. | | `scope` | query | string | não | `everyone` (padrão) ou `me`. | * curl ```bash # Apagar para todos curl -X DELETE https://platform.toptalking.com.br/v1/messages/0dd803ad-1be8-4d87-8cf8-3b3d0e94de93 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" # Só esconder do painel curl -X DELETE "https://platform.toptalking.com.br/v1/messages/0dd803ad-1be8-4d87-8cf8-3b3d0e94de93?scope=me" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js async function apagarMensagem(messageId, scope = 'everyone') { const res = await fetch( `https://platform.toptalking.com.br/v1/messages/${messageId}?scope=${scope}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` } }, ); const body = await res.json(); if (res.status === 409 && scope === 'everyone') { return apagarMensagem(messageId, 'me'); // passou de 48 h ou é recebida: só esconde } if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return body; } await apagarMensagem('0dd803ad-1be8-4d87-8cf8-3b3d0e94de93'); ``` * Python ```python import os import requests message_id = "0dd803ad-1be8-4d87-8cf8-3b3d0e94de93" res = requests.delete( f"https://platform.toptalking.com.br/v1/messages/{message_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"scope": "everyone"}, # ou "me" timeout=30, ) mensagem = res.json() ``` #### Resposta `200` A mensagem apagada (com `scope=everyone`): ```json { "id": "0dd803ad-1be8-4d87-8cf8-3b3d0e94de93", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Mensagem apagada", "media": null, "status": "delivered", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:41:00Z", "edited_at": null } ``` Com `scope=me`, a resposta é a mensagem como estava (o texto não muda): ela só deixa de aparecer no painel e no histórico. #### Erros | HTTP | `code` | Quando acontece | | ---- | --------------------- | -------------------------------------------------------------------------------------------------- | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | A mensagem não existe. | | 409 | `invalid_state` | Com `scope=everyone`: a mensagem foi recebida (`inbound`) ou já passaram 48 horas. Use `scope=me`. | | 422 | `channel_unavailable` | Com `scope=everyone`: o WhatsApp da conversa está desconectado. | | 502 | `provider_error` | O WhatsApp recusou apagar a mensagem. | 409 invalid_state ```json { "error": { "type": "conflict_error", "code": "invalid_state", "message": "Só é possível apagar para todos mensagens enviadas há no máximo 48 horas. Para esconder do painel, use scope=me.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Reagir a uma mensagem POST`/v1/messages/{message_id}/reaction`Testar Envia uma reação com emoji a uma mensagem da conversa — recebida ou enviada —, como o “segurar e reagir” do WhatsApp. Útil para confirmar que viu um pedido sem mandar uma mensagem nova. Enviar `emoji` vazio (`""`) **remove** a reação. Não disponível para conversas do Chat do site (`webchat`). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ------ | ----------- | ------------------------------------------------- | | `message_id` | caminho | UUID | sim | ID da mensagem que vai receber a reação. | | `emoji` | corpo | string | sim | **Um** emoji (ex.: `"👍"`). `""` remove a reação. | * curl ```bash curl https://platform.toptalking.com.br/v1/messages/b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba/reaction \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "emoji": "👍" }' ``` * JavaScript ```js async function reagir(messageId, emoji) { const res = await fetch(`https://platform.toptalking.com.br/v1/messages/${messageId}/reaction`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ emoji }), // '' remove }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return body; } await reagir('b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba', '👍'); ``` * Python ```python import os import requests message_id = "b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba" res = requests.post( f"https://platform.toptalking.com.br/v1/messages/{message_id}/reaction", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={"emoji": "👍"}, # "" remove a reação timeout=30, ) res.raise_for_status() mensagem = res.json() ``` #### Resposta `200` A mensagem com a reação em `reaction`: ```json { "id": "b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "inbound", "type": "text", "text": "Vocês entregam no sábado?", "media": null, "status": "delivered", "error": null, "reply_to_id": null, "reaction": "👍", "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:12:40Z", "edited_at": null } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | --------------------- | ---------------------------------------------------------------------------------------------------------- | | 400 | `missing_field` | Faltou `emoji`. | | 400 | `invalid_field` | Mais de um emoji, ou a conversa é do Chat do site. | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | A mensagem não existe. | | 409 | `invalid_state` | A mensagem ainda não tem identificador no WhatsApp (ex.: ainda está `queued`). Tente de novo em instantes. | | 422 | `channel_unavailable` | O WhatsApp da conversa está desconectado. | | 502 | `provider_error` | O WhatsApp recusou a reação. | 400 invalid_field ```json { "error": { "type": "invalid_request_error", "code": "invalid_field", "message": "Reações não estão disponíveis no Chat do site.", "request_id": "req_6f1d2c3b4a5e" } } ``` # Grupos > Grupos do WhatsApp na API da TopTalking - listar grupos (GET /groups), ver participantes (GET /groups/{group_id}/participants), ler o histórico e enviar mensagens para o grupo usando o id do grupo como contact_id. Os grupos do WhatsApp em que o número conectado participa aparecem no painel como conversas. Na API, **um grupo também é uma conversa**: o `id` do grupo é um `contact_id`. Com ele você: * lê o histórico com [`GET /contacts/{contact_id}/messages`](#ler-as-mensagens-do-grupo); * envia mensagens com [`POST /messages`](#enviar-mensagem-para-o-grupo) e `{ "contact_id": "" }`; * busca, arquiva ou atribui a conversa com os endpoints de [Contatos](/referencia/contatos/) (o contato do grupo tem `is_group: true`). Grupos não são atendidos pelos agentes de IA. ## O objeto grupo ```json { "id": "6fa72198-41f5-4c7f-8fc5-5c84c28e7e00", "object": "group", "name": "Equipe de Vendas", "jid": "120363012345678901@g.us", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "unread_count": 3, "last_message_at": "2026-11-11T13:58:10Z", "last_message_preview": "Fechei mais 2 pedidos hoje 🎉", "archived_at": null, "created_at": "2026-04-01T12:00:00Z" } ``` | Campo | Tipo | Descrição | | ---------------------- | ---------------- | ---------------------------------------------------------------------------------------------------- | | `id` | UUID | ID do grupo. **É o `contact_id` da conversa do grupo**: use para ler o histórico e enviar mensagens. | | `object` | string | Sempre `group`. | | `name` | string ou `null` | Nome do grupo no WhatsApp. | | `jid` | string ou `null` | Identificador do grupo no WhatsApp (termina em `@g.us`). | | `channel_id` | UUID ou `null` | Canal (número de WhatsApp) que participa do grupo. | | `unread_count` | inteiro | Mensagens não lidas no painel. | | `last_message_at` | data ou `null` | Data da última mensagem. | | `last_message_preview` | string ou `null` | Prévia da última mensagem. | | `archived_at` | data ou `null` | Quando a conversa do grupo foi arquivada (`null` = ativa). | | `created_at` | data | Quando o grupo apareceu na TopTalking. | ## Listar grupos GET`/v1/groups`Testar Lista os grupos que aparecem no painel, **dos mais recentes para os mais antigos**, com [paginação](/comecando/paginacao/). Use para descobrir o `id` de um grupo pelo nome antes de ler ou enviar mensagens. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ----- | ------- | ----------- | -------------------------------------------- | | `search` | query | string | não | Parte do nome do grupo (até 100 caracteres). | | `channel_id` | query | UUID | não | Só grupos deste canal. | | `limit` | query | inteiro | não | Itens por página, 1 a 100 (padrão 20). | | `cursor` | query | string | não | `next_cursor` da página anterior. | * curl ```bash curl "https://platform.toptalking.com.br/v1/groups?search=vendas" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const url = new URL('https://platform.toptalking.com.br/v1/groups'); url.searchParams.set('search', 'vendas'); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: grupos } = await res.json(); const grupoId = grupos[0]?.id; // use como contact_id ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/groups", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"search": "vendas"}, timeout=30, ) res.raise_for_status() grupos = res.json()["data"] ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "6fa72198-41f5-4c7f-8fc5-5c84c28e7e00", "object": "group", "name": "Equipe de Vendas", "jid": "120363012345678901@g.us", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "unread_count": 3, "last_message_at": "2026-11-11T13:58:10Z", "last_message_preview": "Fechei mais 2 pedidos hoje 🎉", "archived_at": null, "created_at": "2026-04-01T12:00:00Z" }, { "id": "8255ed43-3d44-4909-bc51-f7a9e810f214", "object": "group", "name": "Fornecedores - Vendas", "jid": "120363029876543210@g.us", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "unread_count": 0, "last_message_at": "2026-11-08T17:22:05Z", "last_message_preview": "O lote de tênis chega na terça.", "archived_at": null, "created_at": "2026-05-12T09:40:00Z" } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## Participantes do grupo GET`/v1/groups/{group_id}/participants`Testar Busca **na hora, no WhatsApp conectado**, quem está no grupo e quem é administrador. Por isso precisa do canal do grupo **conectado** — se ele estiver desconectado, a resposta é `422 channel_unavailable`. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ---------- | ------- | ---- | ----------- | ----------------------------------- | | `group_id` | caminho | UUID | sim | ID do grupo (o mesmo `contact_id`). | A lista vem inteira numa página (`has_more: false`). * curl ```bash curl https://platform.toptalking.com.br/v1/groups/6fa72198-41f5-4c7f-8fc5-5c84c28e7e00/participants \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const groupId = '6fa72198-41f5-4c7f-8fc5-5c84c28e7e00'; const res = await fetch( `https://platform.toptalking.com.br/v1/groups/${groupId}/participants`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` } }, ); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); const admins = body.data.filter((p) => p.role !== 'member'); ``` * Python ```python import os import requests group_id = "6fa72198-41f5-4c7f-8fc5-5c84c28e7e00" res = requests.get( f"https://platform.toptalking.com.br/v1/groups/{group_id}/participants", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() participantes = res.json()["data"] ``` #### Resposta `200` ```json { "object": "list", "data": [ { "object": "group_participant", "phone": "5511988887777", "name": "Loja Aurora", "role": "superadmin", "avatar_url": "https://arquivos.exemplo.com/avatars/loja-aurora.jpg" }, { "object": "group_participant", "phone": "5511991112222", "name": "Carla Mendes", "role": "admin", "avatar_url": null }, { "object": "group_participant", "phone": "5521987654321", "name": "João Pereira", "role": "member", "avatar_url": null } ], "has_more": false, "next_cursor": null } ``` | Campo | Tipo | Descrição | | ------------ | ---------------- | -------------------------------------------------------------------------------------- | | `object` | string | Sempre `group_participant`. | | `phone` | string ou `null` | Telefone do participante, só dígitos com DDI. | | `name` | string ou `null` | Nome do participante, quando conhecido. | | `role` | string | `member` (participante), `admin` (administrador) ou `superadmin` (quem criou o grupo). | | `avatar_url` | string ou `null` | Foto de perfil, quando disponível. | #### Erros | HTTP | `code` | Quando acontece | | ---- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | 404 | `resource_not_found` | O grupo não existe ou é de outra organização. | | 422 | `channel_unavailable` | O WhatsApp do grupo está desconectado. [Reconecte o canal](/referencia/canais/#reconectar-um-whatsapp-desconectado) e tente de novo. | | 502 | `provider_error` | O WhatsApp não respondeu à consulta. Tente de novo em instantes. | 422 channel_unavailable ```json { "error": { "type": "channel_error", "code": "channel_unavailable", "message": "WhatsApp desconectado. Reconecte no painel.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Ler as mensagens do grupo Use o `id` do grupo em [`GET /contacts/{contact_id}/messages`](/referencia/contatos/#hist%C3%B3rico-da-conversa). Em grupos, `sender_name` diz **quem mandou** cada mensagem recebida. GET`/v1/contacts/{contact_id}/messages`Testar * curl ```bash curl "https://platform.toptalking.com.br/v1/contacts/6fa72198-41f5-4c7f-8fc5-5c84c28e7e00/messages?limit=20" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const groupId = '6fa72198-41f5-4c7f-8fc5-5c84c28e7e00'; const res = await fetch( `https://platform.toptalking.com.br/v1/contacts/${groupId}/messages?limit=20`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` } }, ); const { data: mensagens } = await res.json(); for (const m of mensagens) console.log(`${m.sender_name ?? 'Nós'}: ${m.text}`); ``` * Python ```python import os import requests group_id = "6fa72198-41f5-4c7f-8fc5-5c84c28e7e00" res = requests.get( f"https://platform.toptalking.com.br/v1/contacts/{group_id}/messages", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"limit": 20}, timeout=30, ) res.raise_for_status() for m in res.json()["data"]: print(f"{m['sender_name'] or 'Nós'}: {m['text']}") ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "cb42b3fb-9e83-46aa-b7f6-f3aca950abf5", "object": "message", "contact_id": "6fa72198-41f5-4c7f-8fc5-5c84c28e7e00", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "inbound", "type": "text", "text": "Fechei mais 2 pedidos hoje 🎉", "media": null, "status": "delivered", "error": null, "reply_to_id": null, "reaction": null, "sender_name": "Carla Mendes", "transcript": null, "created_at": "2026-11-11T13:58:10Z", "edited_at": null } ], "has_more": true, "next_cursor": "eyJ0IjoiMjAyNi0xMS0xMVQxMzo1ODoxMFoifQ" } ``` ## Enviar mensagem para o grupo Use [`POST /messages`](/referencia/mensagens/#enviar-mensagem) com o `id` do grupo em `contact_id` (`to` é só para telefones). Texto, mídia e respostas citando (`reply_to_id`) funcionam como numa conversa individual. POST`/v1/messages`Testar * curl ```bash curl https://platform.toptalking.com.br/v1/messages \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: meta-diaria-2026-11-11" \ -d '{ "contact_id": "6fa72198-41f5-4c7f-8fc5-5c84c28e7e00", "text": "Bom dia, time! Meta de hoje: 15 pedidos. Bora! 💪" }' ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'meta-diaria-2026-11-11', }, body: JSON.stringify({ contact_id: '6fa72198-41f5-4c7f-8fc5-5c84c28e7e00', // id do grupo text: 'Bom dia, time! Meta de hoje: 15 pedidos. Bora! 💪', }), }); const mensagem = await res.json(); ``` * Python ```python import os import requests res = requests.post( "https://platform.toptalking.com.br/v1/messages", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": "meta-diaria-2026-11-11", }, json={ "contact_id": "6fa72198-41f5-4c7f-8fc5-5c84c28e7e00", # id do grupo "text": "Bom dia, time! Meta de hoje: 15 pedidos. Bora! 💪", }, timeout=30, ) res.raise_for_status() mensagem = res.json() ``` #### Resposta `201` ```json { "id": "37e1fdb6-6cd4-4345-bbaa-58216b232122", "object": "message", "contact_id": "6fa72198-41f5-4c7f-8fc5-5c84c28e7e00", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Bom dia, time! Meta de hoje: 15 pedidos. Bora! 💪", "media": null, "status": "sent", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T11:00:03Z", "edited_at": null } ``` Os erros são os de [`POST /messages`](/referencia/mensagens/#enviar-mensagem). O mais comum em grupos é `422 channel_unavailable`, quando o WhatsApp do grupo está desconectado. Grupos também têm regras Mensagens automáticas em grupos grandes, ou em grupos de pessoas que não esperam receber, podem levar a denúncias e ao banimento do número. Leia [Boas práticas no WhatsApp](/comecando/boas-praticas-whatsapp/). ## Receber mensagens de grupos Mensagens recebidas em grupos chegam no webhook [`message.received`](/webhooks/eventos/#messagereceived) como as demais: `data.contact.is_group` é `true`, `data.contact.id` é o `id` do grupo e `data.message.sender_name` diz quem mandou. Para listar só conversas de grupo (ou só individuais) junto com os demais filtros de contato, use [`GET /contacts?is_group=true`](/referencia/contatos/#listar-contatos). # Kanban > Etapas do Kanban (pipeline) na API da TopTalking - listar colunas com GET /stages, mover cards com PATCH /contacts/{contact_id} e stage_id, tirar da etapa e finalizar conversas na etapa final. O **Kanban** da TopTalking organiza as conversas em etapas (colunas), como um funil de vendas ou de atendimento. Cada contato está em **no máximo uma etapa**. As etapas são configuradas no painel em Configurações → Etapas do Kanban. A etapa com `is_final: true` (normalmente “Finalizado”) é especial: **mover um card para ela finaliza a conversa**, igual ao painel. ## O objeto etapa ```json { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "object": "stage", "name": "Negociação", "color": "#8b5cf6", "position": 2, "is_final": false } ``` | Campo | Tipo | Descrição | | ---------- | -------- | -------------------------------------------------------------------------- | | `id` | UUID | ID da etapa. Use em `stage_id`. | | `object` | string | Sempre `stage`. | | `name` | string | Nome da coluna. | | `color` | string | Cor da coluna no painel (hexadecimal). | | `position` | inteiro | Ordem da coluna (0 = primeira). | | `is_final` | booleano | Etapa terminal (“Finalizado”). Mover um card para ela finaliza a conversa. | Nos contatos e nos webhooks, a etapa aparece resumida (`id`, `name`, `is_final`). ## Listar etapas GET`/v1/stages`Testar Devolve as etapas **na ordem das colunas**. Use para descobrir o `id` de cada etapa antes de mover cards ou filtrar contatos. Não tem parâmetros. A lista vem inteira numa página (`has_more: false`). * curl ```bash curl https://platform.toptalking.com.br/v1/stages \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/stages', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: etapas } = await res.json(); const porNome = Object.fromEntries(etapas.map((e) => [e.name, e.id])); // porNome['Negociação'] → "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11" ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/stages", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() por_nome = {e["name"]: e["id"] for e in res.json()["data"]} # por_nome["Negociação"] -> "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11" ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "e63d8231-3992-457e-a8b0-d1be2fa591d4", "object": "stage", "name": "Novo lead", "color": "#3b82f6", "position": 0, "is_final": false }, { "id": "5f861a0a-9667-4215-b498-3be9da314b60", "object": "stage", "name": "Em atendimento", "color": "#f59e0b", "position": 1, "is_final": false }, { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "object": "stage", "name": "Negociação", "color": "#8b5cf6", "position": 2, "is_final": false }, { "id": "3c671a88-07fb-4ccc-80db-184be834360c", "object": "stage", "name": "Finalizado", "color": "#22c55e", "position": 3, "is_final": true } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## Mover um card Para mover um contato de etapa, use [`PATCH /contacts/{contact_id}`](/referencia/contatos/#atualizar-contato-e-mover-no-kanban) com `stage_id`: PATCH`/v1/contacts/{contact_id}`Testar | Corpo | Efeito | | --------------------------------------- | -------------------------------------------------- | | `{ "stage_id": "" }` | Move o card para a etapa. | | `{ "stage_id": "" }` | Move para a etapa final **e finaliza a conversa**. | | `{ "stage_id": null }` | Tira o card de qualquer etapa. | * curl ```bash curl -X PATCH https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "stage_id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11" }' ``` * JavaScript ```js async function moverCard(contactId, stageId) { const res = await fetch(`https://platform.toptalking.com.br/v1/contacts/${contactId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ stage_id: stageId }), // null tira de qualquer etapa }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return body; // contato atualizado, com body.stage } await moverCard('3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60', '9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11'); ``` * Python ```python import os import requests def mover_card(contact_id: str, stage_id: str | None) -> dict: res = requests.patch( f"https://platform.toptalking.com.br/v1/contacts/{contact_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={"stage_id": stage_id}, # None tira de qualquer etapa timeout=30, ) body = res.json() if not res.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}") return body # contato atualizado, com body["stage"] mover_card("3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11") ``` A resposta `200` é o [contato atualizado](/referencia/contatos/#o-objeto-contato), já com o novo `stage`. Um `stage_id` que não é etapa da organização responde `400 invalid_field` (`param: "stage_id"`); um contato inexistente, `404 resource_not_found`. ## Ler um quadro inteiro Para montar o seu próprio Kanban, busque as etapas e depois os contatos de cada uma com o filtro `stage_id`: ```js const API = 'https://platform.toptalking.com.br/v1'; const headers = { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }; const { data: etapas } = await (await fetch(`${API}/stages`, { headers })).json(); const quadro = []; for (const etapa of etapas) { const url = `${API}/contacts?stage_id=${etapa.id}&archived=false&limit=100`; const { data: cards } = await (await fetch(url, { headers })).json(); quadro.push({ ...etapa, cards }); } ``` ## Ser avisado quando um card muda de etapa Assine o webhook [`contact.stage_changed`](/webhooks/eventos/#contactstage_changed). Ele chega toda vez que um card muda de coluna — pelo painel, pela IA ou pela API — com a etapa nova e a anterior. # Mensagens agendadas > Agende mensagens de WhatsApp pela API da TopTalking (POST /scheduled-messages) com variáveis {{nome}} e {{telefone}} e anexos, liste, consulte e cancele - as mesmas mensagens da Agenda do painel. Mensagens agendadas são as mesmas da **Agenda** do painel: o que você agenda pela API aparece lá, e a equipe pode ver e acompanhar. O despachante verifica a fila **a cada minuto** e envia as mensagens que chegaram no horário. Use para lembretes de consulta, cobranças, follow-ups e renovações. * `send_at` precisa estar **no futuro** e em **até 365 dias**. * O texto aceita as variáveis `{{nome}}` e `{{telefone}}`, preenchidas com os dados do contato na hora do envio. * Até **10 anexos**; o texto vira a legenda do primeiro anexo que aceita legenda. * Não disponível para conversas do **Chat do site** (`webchat`) nem para grupos. ## O objeto mensagem agendada ```json { "id": "603916c7-604c-4c56-8a58-f3f76886634b", "object": "scheduled_message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "to": "5511999998888", "title": "Lembrete de consulta", "text": "Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h.", "media": [], "send_at": "2026-12-01T15:00:00Z", "status": "pending", "sent_at": null, "message_id": null, "error": null, "created_at": "2026-11-11T14:50:00Z", "updated_at": "2026-11-11T14:50:00Z" } ``` | Campo | Tipo | Descrição | | ------------ | ---------------- | -------------------------------------------------------- | | `id` | UUID | ID da mensagem agendada. | | `object` | string | Sempre `scheduled_message`. | | `contact_id` | UUID ou `null` | Contato de destino. | | `to` | string ou `null` | Telefone de destino. | | `title` | string | Título na Agenda (padrão: “Mensagem via API”). | | `text` | string ou `null` | Texto, com as variáveis ainda não substituídas. | | `media` | lista | Anexos: cada um com `url`, `mime_type` e `filename`. | | `send_at` | data | Quando deve ser enviada. | | `status` | string | `pending`, `processing`, `sent`, `failed` ou `canceled`. | | `sent_at` | data ou `null` | Quando foi enviada. | | `message_id` | UUID ou `null` | A [mensagem](/referencia/mensagens/) gerada no envio. | | `error` | string ou `null` | Motivo da falha, quando `status` é `failed`. | | `created_at` | data | Criação. | | `updated_at` | data | Última alteração. | | Status | Significado | | ------------ | ------------------------------------------------------------- | | `pending` | Esperando o horário. É o único status que pode ser cancelado. | | `processing` | Sendo enviada agora. | | `sent` | Enviada (veja `sent_at` e `message_id`). | | `failed` | Não foi possível enviar (veja `error`). | | `canceled` | Cancelada antes do envio. | ## Agendar mensagem POST`/v1/scheduled-messages`Testar Programa uma mensagem para sair numa data e hora. Ela aparece na Agenda do painel e é enviada no horário pelo WhatsApp da conversa. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ----------------- | --------- | ------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `to` | corpo | string | `to` ou `contact_id` | Telefone com DDI e DDD. | | `contact_id` | corpo | UUID | `to` ou `contact_id` | Contato de destino. | | `send_at` | corpo | data | sim | ISO 8601 **com fuso** (ex.: `2026-12-01T12:00:00-03:00`). No futuro e em até 365 dias. | | `text` | corpo | string | `text` e/ou `media` | Até 4096 caracteres. Aceita `{{nome}}` e `{{telefone}}`. | | `media` | corpo | lista | `text` e/ou `media` | Até 10 anexos, cada um com `url` (HTTPS pública), `type` (`image`, `video`, `audio`, `document`) e, opcionalmente, `filename`, `mime_type` e `voice_note`. O texto vira legenda do primeiro anexo que aceita legenda. | | `title` | corpo | string | não | Título na Agenda (até 120 caracteres). Padrão: “Mensagem via API”. | | `Idempotency-Key` | cabeçalho | string | não (recomendado) | Evita agendar duas vezes numa retentativa. | Sempre informe o fuso `2026-12-01T12:00:00-03:00` é meio-dia em Brasília. Sem o fuso, a data é ambígua e a API recusa. Na resposta, `send_at` pode vir convertido para UTC (`2026-12-01T15:00:00Z`) — é o mesmo instante. * curl ```bash curl https://platform.toptalking.com.br/v1/scheduled-messages \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: consulta-987-lembrete" \ -d '{ "to": "5511999998888", "send_at": "2026-12-01T12:00:00-03:00", "title": "Lembrete de consulta", "text": "Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h." }' ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/scheduled-messages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'consulta-987-lembrete-mapa', }, body: JSON.stringify({ contact_id: '3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60', send_at: '2026-12-01T12:00:00-03:00', title: 'Lembrete de consulta', text: 'Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h. Segue o endereço:', media: [{ url: 'https://exemplo.com/mapa-clinica.png', type: 'image' }], }), }); const agendada = await res.json(); console.log(agendada.id, agendada.status); // "pending" ``` * Python ```python import os import requests res = requests.post( "https://platform.toptalking.com.br/v1/scheduled-messages", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": "consulta-987-lembrete", }, json={ "to": "5511999998888", "send_at": "2026-12-01T12:00:00-03:00", "title": "Lembrete de consulta", "text": "Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h.", }, timeout=30, ) agendada = res.json() if not res.ok: raise RuntimeError(f"{agendada['error']['code']}: {agendada['error']['message']}") ``` #### Resposta `201` ```json { "id": "603916c7-604c-4c56-8a58-f3f76886634b", "object": "scheduled_message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "to": "5511999998888", "title": "Lembrete de consulta", "text": "Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h.", "media": [], "send_at": "2026-12-01T15:00:00Z", "status": "pending", "sent_at": null, "message_id": null, "error": null, "created_at": "2026-11-11T14:50:00Z", "updated_at": "2026-11-11T14:50:00Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `missing_field` | Faltou `send_at`, `to`/`contact_id` ou `text`/`media`. | | 400 | `invalid_field` | Campo inválido: `send_at` sem fuso, no passado ou a mais de 365 dias, mais de 10 anexos, texto longo demais, conversa do Chat do site ou de grupo… | | 400 | `invalid_phone` | Telefone inválido. | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | `contact_id` não encontrado. | | 409 | `idempotency_conflict` | `Idempotency-Key` reutilizada com outro corpo. | 400 invalid_field ```json { "error": { "type": "invalid_request_error", "code": "invalid_field", "message": "`send_at` precisa estar no futuro.", "param": "send_at", "request_id": "req_6f1d2c3b4a5e" } } ``` Quando a mensagem for enviada (ou falhar), você recebe os webhooks [`scheduled_message.sent`](/webhooks/eventos/#scheduled_messagesent) ou [`scheduled_message.failed`](/webhooks/eventos/#scheduled_messagefailed). ## Listar mensagens agendadas GET`/v1/scheduled-messages`Testar As mensagens agendadas, ordenadas pelo horário de envio (as mais próximas primeiro). Use para mostrar a agenda de um cliente ou conferir o que ainda vai sair. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ----- | ------- | ----------- | -------------------------------------------------------- | | `status` | query | string | não | `pending`, `processing`, `sent`, `failed` ou `canceled`. | | `contact_id` | query | UUID | não | Só as deste contato. | | `limit` | query | inteiro | não | Itens por página, 1 a 100 (padrão 20). | | `cursor` | query | string | não | `next_cursor` da página anterior. | * curl ```bash curl "https://platform.toptalking.com.br/v1/scheduled-messages?status=pending&limit=50" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch( 'https://platform.toptalking.com.br/v1/scheduled-messages?status=pending&limit=50', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` } }, ); const { data: pendentes } = await res.json(); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/scheduled-messages", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"status": "pending", "limit": 50}, timeout=30, ) res.raise_for_status() pendentes = res.json()["data"] ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "603916c7-604c-4c56-8a58-f3f76886634b", "object": "scheduled_message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "to": "5511999998888", "title": "Lembrete de consulta", "text": "Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h.", "media": [], "send_at": "2026-12-01T15:00:00Z", "status": "pending", "sent_at": null, "message_id": null, "error": null, "created_at": "2026-11-11T14:50:00Z", "updated_at": "2026-11-11T14:50:00Z" } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## Buscar mensagem agendada GET`/v1/scheduled-messages/{scheduled_message_id}`Testar Uma mensagem agendada pelo ID. Use para saber se já saiu (`status`, `sent_at`) e qual mensagem ela gerou (`message_id`). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ---------------------- | ------- | ---- | ----------- | ------------------------ | | `scheduled_message_id` | caminho | UUID | sim | ID da mensagem agendada. | * curl ```bash curl https://platform.toptalking.com.br/v1/scheduled-messages/d3440bed-1a6f-4470-9d89-cec62fe2fc67 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const id = 'd3440bed-1a6f-4470-9d89-cec62fe2fc67'; const res = await fetch(`https://platform.toptalking.com.br/v1/scheduled-messages/${id}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const agendada = await res.json(); ``` * Python ```python import os import requests scheduled_id = "d3440bed-1a6f-4470-9d89-cec62fe2fc67" res = requests.get( f"https://platform.toptalking.com.br/v1/scheduled-messages/{scheduled_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() agendada = res.json() ``` #### Resposta `200` Exemplo de uma mensagem que já foi enviada: ```json { "id": "d3440bed-1a6f-4470-9d89-cec62fe2fc67", "object": "scheduled_message", "contact_id": "721bfe94-e549-4354-b90c-a8d679e9b271", "to": "5521987654321", "title": "Renovação do plano", "text": "Oi {{nome}}! Seu plano vence na sexta. Quer que a gente renove?", "media": [], "send_at": "2026-11-10T13:00:00Z", "status": "sent", "sent_at": "2026-11-10T13:00:41Z", "message_id": "8e395744-cc4c-4eae-9ccd-06c8dde834e0", "error": null, "created_at": "2026-11-03T18:22:10Z", "updated_at": "2026-11-10T13:00:41Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ------------------------------- | | 404 | `resource_not_found` | A mensagem agendada não existe. | ## Cancelar mensagem agendada DELETE`/v1/scheduled-messages/{scheduled_message_id}`Testar Cancela uma mensagem que **ainda está `pending`** — ela some da fila e fica como cancelada na Agenda. A resposta é o item com `status: "canceled"`. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ---------------------- | ------- | ---- | ----------- | ------------------------ | | `scheduled_message_id` | caminho | UUID | sim | ID da mensagem agendada. | * curl ```bash curl -X DELETE https://platform.toptalking.com.br/v1/scheduled-messages/603916c7-604c-4c56-8a58-f3f76886634b \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const id = '603916c7-604c-4c56-8a58-f3f76886634b'; const res = await fetch(`https://platform.toptalking.com.br/v1/scheduled-messages/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const body = await res.json(); if (res.status === 409) console.log('Não dá mais para cancelar:', body.error.message); ``` * Python ```python import os import requests scheduled_id = "603916c7-604c-4c56-8a58-f3f76886634b" res = requests.delete( f"https://platform.toptalking.com.br/v1/scheduled-messages/{scheduled_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) body = res.json() if res.status_code == 409: print("Não dá mais para cancelar:", body["error"]["message"]) ``` #### Resposta `200` ```json { "id": "603916c7-604c-4c56-8a58-f3f76886634b", "object": "scheduled_message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "to": "5511999998888", "title": "Lembrete de consulta", "text": "Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h.", "media": [], "send_at": "2026-12-01T15:00:00Z", "status": "canceled", "sent_at": null, "message_id": null, "error": null, "created_at": "2026-11-11T14:50:00Z", "updated_at": "2026-11-11T15:02:13Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------------------------------------------------------------------------------------- | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | A mensagem agendada não existe. | | 409 | `invalid_state` | A mensagem não está mais `pending` (já foi enviada, está sendo enviada, falhou ou já foi cancelada). | 409 invalid_state ```json { "error": { "type": "conflict_error", "code": "invalid_state", "message": "Só é possível cancelar mensagens pendentes (status atual: sent).", "request_id": "req_6f1d2c3b4a5e" } } ``` # Equipe > Membros da organização na API da TopTalking (GET /members e GET /members/{member_id}) e como atribuir uma conversa a um atendente com PATCH /contacts/{contact_id} e assigned_to. Os **membros** são as pessoas da equipe que usam o painel: donos, administradores e atendentes. Pela API você lista a equipe e usa o `id` de cada membro para **atribuir conversas** (`assigned_to`) e para filtrar as conversas de cada atendente. Convidar, remover e mudar o papel de membros é feito no painel. ## O objeto membro ```json { "id": "4083deb8-f96e-4093-8598-ebdd4dee786b", "object": "member", "name": "Carla Mendes", "email": "carla@lojaaurora.com.br", "role": "agent", "avatar_url": "https://arquivos.exemplo.com/avatars/carla.jpg", "joined_at": "2026-03-10T13:00:00Z" } ``` | Campo | Tipo | Descrição | | ------------ | ---------------- | ------------------------------------------------------------------------ | | `id` | UUID | ID do usuário. **Use em `assigned_to`** para atribuir conversas. | | `object` | string | Sempre `member`. | | `name` | string ou `null` | Nome da pessoa. | | `email` | string ou `null` | E-mail de login. | | `role` | string | `owner` (dono da conta), `admin` (administrador) ou `agent` (atendente). | | `avatar_url` | string ou `null` | Foto de perfil. | | `joined_at` | data | Quando entrou na organização. | ## Listar membros GET`/v1/members`Testar Todos os membros da organização. Use para montar um seletor de responsável ou para descobrir o `id` de um atendente pelo nome ou e-mail. Não tem parâmetros. A lista vem inteira numa página (`has_more: false`). * curl ```bash curl https://platform.toptalking.com.br/v1/members \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/members', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: equipe } = await res.json(); const carla = equipe.find((m) => m.email === 'carla@lojaaurora.com.br'); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/members", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() equipe = res.json()["data"] carla = next(m for m in equipe if m["email"] == "carla@lojaaurora.com.br") ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "4ce0bb29-e6f6-4a78-8d63-747e73e00071", "object": "member", "name": "Rafael Costa", "email": "rafael@lojaaurora.com.br", "role": "owner", "avatar_url": "https://arquivos.exemplo.com/avatars/rafael.jpg", "joined_at": "2026-02-10T12:00:00Z" }, { "id": "384d33f5-953e-4d07-a85e-69e67e1bf89c", "object": "member", "name": "Bruno Alves", "email": "bruno@lojaaurora.com.br", "role": "admin", "avatar_url": null, "joined_at": "2026-02-15T18:30:00Z" }, { "id": "4083deb8-f96e-4093-8598-ebdd4dee786b", "object": "member", "name": "Carla Mendes", "email": "carla@lojaaurora.com.br", "role": "agent", "avatar_url": "https://arquivos.exemplo.com/avatars/carla.jpg", "joined_at": "2026-03-10T13:00:00Z" } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## Buscar membro GET`/v1/members/{member_id}`Testar Um membro pelo ID. Útil para mostrar o nome do responsável de uma conversa a partir do `assigned_to` do contato. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ----------- | ------- | ---- | ----------- | ---------------------------------------------- | | `member_id` | caminho | UUID | sim | ID do membro (o mesmo valor de `assigned_to`). | * curl ```bash curl https://platform.toptalking.com.br/v1/members/4083deb8-f96e-4093-8598-ebdd4dee786b \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const memberId = '4083deb8-f96e-4093-8598-ebdd4dee786b'; const res = await fetch(`https://platform.toptalking.com.br/v1/members/${memberId}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const membro = await res.json(); console.log(`Responsável: ${membro.name}`); ``` * Python ```python import os import requests member_id = "4083deb8-f96e-4093-8598-ebdd4dee786b" res = requests.get( f"https://platform.toptalking.com.br/v1/members/{member_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() membro = res.json() ``` #### Resposta `200` ```json { "id": "4083deb8-f96e-4093-8598-ebdd4dee786b", "object": "member", "name": "Carla Mendes", "email": "carla@lojaaurora.com.br", "role": "agent", "avatar_url": "https://arquivos.exemplo.com/avatars/carla.jpg", "joined_at": "2026-03-10T13:00:00Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | --------------------------------------------------------------------------- | | 404 | `resource_not_found` | Não há membro com esse ID nesta organização (ex.: a pessoa saiu da equipe). | ## Atribuir uma conversa Para definir o **responsável** por uma conversa, envie o `id` do membro em `assigned_to` com [`PATCH /contacts/{contact_id}`](/referencia/contatos/#atualizar-contato-e-mover-no-kanban). A conversa passa a aparecer para ele no painel como “minha”. PATCH`/v1/contacts/{contact_id}`Testar | Corpo | Efeito | | ------------------------------------- | ----------------------------------------- | | `{ "assigned_to": "" }` | Atribui a conversa a este membro. | | `{ "assigned_to": null }` | Libera a conversa (fica sem responsável). | * curl ```bash curl -X PATCH https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b" }' ``` * JavaScript ```js async function atribuir(contactId, memberId) { const res = await fetch(`https://platform.toptalking.com.br/v1/contacts/${contactId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ assigned_to: memberId }), // null libera a conversa }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return body; } await atribuir('3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60', '4083deb8-f96e-4093-8598-ebdd4dee786b'); ``` * Python ```python import os import requests contact_id = "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60" res = requests.patch( f"https://platform.toptalking.com.br/v1/contacts/{contact_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={"assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b"}, # None libera timeout=30, ) res.raise_for_status() contato = res.json() ``` A resposta `200` é o [contato atualizado](/referencia/contatos/#o-objeto-contato), com o novo `assigned_to`. A atribuição fica registrada no histórico de atribuições da conversa no painel e dispara o webhook [`contact.updated`](/webhooks/eventos/#contactupdated) com `assigned_to` em `changed_fields`. | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------------------------------------------------------- | | 400 | `invalid_field` | `assigned_to` não é um membro da organização (`param: "assigned_to"`). | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O contato não existe. | 400 invalid_field ```json { "error": { "type": "invalid_request_error", "code": "invalid_field", "message": "Este usuário não é membro da organização. Veja GET /v1/members.", "param": "assigned_to", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Conversas de cada atendente Filtre [`GET /contacts`](/referencia/contatos/#listar-contatos) por `assigned_to`: * `?assigned_to=` — as conversas de um atendente; * `?assigned_to=none` — as conversas **sem responsável** (ótimo para distribuir a fila). - curl ```bash # Conversas ativas sem responsável curl "https://platform.toptalking.com.br/v1/contacts?assigned_to=none&archived=false&is_group=false" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` - JavaScript ```js // Distribui as conversas sem responsável entre os atendentes (rodízio simples) const API = 'https://platform.toptalking.com.br/v1'; const headers = { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }; const { data: equipe } = await (await fetch(`${API}/members`, { headers })).json(); const atendentes = equipe.filter((m) => m.role === 'agent'); const url = `${API}/contacts?assigned_to=none&archived=false&is_group=false&limit=50`; const { data: fila } = await (await fetch(url, { headers })).json(); for (const [i, contato] of fila.entries()) { const responsavel = atendentes[i % atendentes.length]; await fetch(`${API}/contacts/${contato.id}`, { method: 'PATCH', headers, body: JSON.stringify({ assigned_to: responsavel.id }), }); } ``` - Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/contacts", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"assigned_to": "none", "archived": "false", "is_group": "false"}, timeout=30, ) res.raise_for_status() sem_responsavel = res.json()["data"] ``` # Agentes de IA > Agentes de IA da TopTalking pela API - listar (GET /ai-agents), buscar, ligar e desligar (PATCH /ai-agents/{agent_id}) e escolher o agente ou desligar a IA em uma conversa com PATCH /contacts/{contact_id}. Os **agentes de IA** respondem as conversas do WhatsApp e do Chat do site sozinhos, seguindo as instruções, FAQs e a base de conhecimento configuradas no painel (Agentes de IA). Pela API você: * lista os agentes e vê qual é o **padrão**; * **liga ou desliga** um agente; * escolhe **qual agente** atende uma conversa, ou **desliga a IA** só naquela conversa (para um humano assumir). Criar agentes e editar instruções, FAQs e base de conhecimento é feito no painel. ## O objeto agente de IA ```json { "id": "523fc881-026e-4e04-89e2-8a99c28607c9", "object": "ai_agent", "name": "Aurora", "description": "Atendimento geral: dúvidas sobre pedidos, entregas e trocas.", "active": true, "is_default": true, "tone": "profissional_caloroso", "strict_mode": false, "created_at": "2026-03-05T14:00:00Z", "updated_at": "2026-11-02T10:15:00Z" } ``` | Campo | Tipo | Descrição | | ------------- | ---------------- | ----------------------------------------------------------------------------------------- | | `id` | UUID | ID do agente. Use em `ai_agent_id` de uma conversa. | | `object` | string | Sempre `ai_agent`. | | `name` | string | Nome do agente. | | `description` | string ou `null` | Descrição livre (para que serve o agente). | | `active` | booleano | Se o agente está respondendo. | | `is_default` | booleano | Agente **padrão**: atende as conversas que não têm agente definido (`ai_agent_id: null`). | | `tone` | string | Tom de voz configurado no painel (ex.: `profissional_caloroso`). | | `strict_mode` | booleano | Modo restrito: só responde com base nas FAQs, na base de conhecimento e nas instruções. | | `created_at` | data | Criação. | | `updated_at` | data | Última alteração. | ## Quem responde uma conversa A IA responde uma conversa quando **as três condições** são verdadeiras: 1. a conversa está com a IA ligada (`ai_enabled: true` no [contato](/referencia/contatos/#o-objeto-contato)); 2. o agente da conversa — o de `ai_agent_id` ou, se for `null`, o agente padrão — está `active: true`; 3. a IA da organização está ligada (ou seja, o agente **padrão** está ativo). Grupos não são atendidos pela IA. ## Listar agentes de IA GET`/v1/ai-agents`Testar Todos os agentes da organização. Use para descobrir o `id` do agente certo antes de atribuí-lo a uma conversa. Não tem parâmetros. A lista vem inteira numa página (`has_more: false`). * curl ```bash curl https://platform.toptalking.com.br/v1/ai-agents \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/ai-agents', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: agentes } = await res.json(); const padrao = agentes.find((a) => a.is_default); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/ai-agents", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() agentes = res.json()["data"] padrao = next(a for a in agentes if a["is_default"]) ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "523fc881-026e-4e04-89e2-8a99c28607c9", "object": "ai_agent", "name": "Aurora", "description": "Atendimento geral: dúvidas sobre pedidos, entregas e trocas.", "active": true, "is_default": true, "tone": "profissional_caloroso", "strict_mode": false, "created_at": "2026-03-05T14:00:00Z", "updated_at": "2026-11-02T10:15:00Z" }, { "id": "52da47ff-662a-4292-9961-27a95fc5ddd8", "object": "ai_agent", "name": "Vendas", "description": "Qualifica leads e apresenta produtos e promoções.", "active": true, "is_default": false, "tone": "descontraido", "strict_mode": true, "created_at": "2026-06-18T16:30:00Z", "updated_at": "2026-10-21T09:05:00Z" } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## Buscar agente de IA GET`/v1/ai-agents/{agent_id}`Testar Um agente pelo ID — por exemplo, para mostrar qual agente atende uma conversa a partir do `ai_agent_id` do contato. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ---------- | ------- | ---- | ----------- | ------------- | | `agent_id` | caminho | UUID | sim | ID do agente. | * curl ```bash curl https://platform.toptalking.com.br/v1/ai-agents/52da47ff-662a-4292-9961-27a95fc5ddd8 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const agentId = '52da47ff-662a-4292-9961-27a95fc5ddd8'; const res = await fetch(`https://platform.toptalking.com.br/v1/ai-agents/${agentId}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const agente = await res.json(); ``` * Python ```python import os import requests agent_id = "52da47ff-662a-4292-9961-27a95fc5ddd8" res = requests.get( f"https://platform.toptalking.com.br/v1/ai-agents/{agent_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() agente = res.json() ``` #### Resposta `200` ```json { "id": "52da47ff-662a-4292-9961-27a95fc5ddd8", "object": "ai_agent", "name": "Vendas", "description": "Qualifica leads e apresenta produtos e promoções.", "active": true, "is_default": false, "tone": "descontraido", "strict_mode": true, "created_at": "2026-06-18T16:30:00Z", "updated_at": "2026-10-21T09:05:00Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------------------------------- | | 404 | `resource_not_found` | O agente não existe ou é de outra organização. | ## Ligar ou desligar agente de IA PATCH`/v1/ai-agents/{agent_id}`Testar Liga (`active: true`) ou desliga (`active: false`) um agente. Um agente desligado para de responder as conversas dele. Desligar o agente padrão desliga a IA da organização inteira O agente com `is_default: true` funciona como o interruptor geral da IA (o mesmo do painel). Com ele desligado, **nenhuma conversa é respondida pela IA**, nem as de outros agentes. Para tirar a IA de **uma** conversa só, use [`ai_enabled: false` no contato](#agente-e-ia-por-conversa). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ---------- | ------- | -------- | ----------- | ----------------------------- | | `agent_id` | caminho | UUID | sim | ID do agente. | | `active` | corpo | booleano | sim | `true` liga; `false` desliga. | * curl ```bash # Desligar o agente "Vendas" (o agente padrão continua respondendo) curl -X PATCH https://platform.toptalking.com.br/v1/ai-agents/52da47ff-662a-4292-9961-27a95fc5ddd8 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "active": false }' ``` * JavaScript ```js async function ligarAgente(agentId, active) { const res = await fetch(`https://platform.toptalking.com.br/v1/ai-agents/${agentId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ active }), }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return body; } await ligarAgente('52da47ff-662a-4292-9961-27a95fc5ddd8', false); ``` * Python ```python import os import requests agent_id = "52da47ff-662a-4292-9961-27a95fc5ddd8" res = requests.patch( f"https://platform.toptalking.com.br/v1/ai-agents/{agent_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={"active": False}, timeout=30, ) res.raise_for_status() agente = res.json() ``` #### Resposta `200` O agente atualizado: ```json { "id": "52da47ff-662a-4292-9961-27a95fc5ddd8", "object": "ai_agent", "name": "Vendas", "description": "Qualifica leads e apresenta produtos e promoções.", "active": false, "is_default": false, "tone": "descontraido", "strict_mode": true, "created_at": "2026-06-18T16:30:00Z", "updated_at": "2026-11-11T16:45:12Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | ------------------------------- | ------------------------------------------------ | | 400 | `missing_field` | Faltou `active`. | | 400 | `invalid_json`, `invalid_field` | Corpo inválido ou `active` não é `true`/`false`. | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O agente não existe ou é de outra organização. | 400 missing_field ```json { "error": { "type": "invalid_request_error", "code": "missing_field", "message": "Informe `active` (true ou false).", "param": "active", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Agente e IA por conversa O agente e a IA de **cada conversa** são campos do contato. Use [`PATCH /contacts/{contact_id}`](/referencia/contatos/#atualizar-contato-e-mover-no-kanban): PATCH`/v1/contacts/{contact_id}`Testar | Corpo | Efeito | | ------------------------------------- | ------------------------------------------------------------ | | `{ "ai_agent_id": "" }` | Esta conversa passa a ser atendida por este agente. | | `{ "ai_agent_id": null }` | Volta para o agente padrão. | | `{ "ai_enabled": false }` | Desliga a IA **só nesta conversa** (para um humano assumir). | | `{ "ai_enabled": true }` | Religa a IA nesta conversa. | * curl ```bash # Passar a conversa para o agente "Vendas" curl -X PATCH https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ai_agent_id": "52da47ff-662a-4292-9961-27a95fc5ddd8" }' # Um humano vai assumir: desligar a IA e atribuir à Carla curl -X PATCH https://platform.toptalking.com.br/v1/contacts/3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ai_enabled": false, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b" }' ``` * JavaScript ```js const contactId = '3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60'; const res = await fetch(`https://platform.toptalking.com.br/v1/contacts/${contactId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, // Um humano vai assumir: desliga a IA e atribui à Carla body: JSON.stringify({ ai_enabled: false, assigned_to: '4083deb8-f96e-4093-8598-ebdd4dee786b' }), }); const contato = await res.json(); ``` * Python ```python import os import requests contact_id = "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60" res = requests.patch( f"https://platform.toptalking.com.br/v1/contacts/{contact_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={"ai_agent_id": "52da47ff-662a-4292-9961-27a95fc5ddd8"}, # None = agente padrão timeout=30, ) res.raise_for_status() contato = res.json() ``` A resposta `200` é o [contato atualizado](/referencia/contatos/#o-objeto-contato). Um `ai_agent_id` que não é agente da organização responde `400 invalid_field` (`param: "ai_agent_id"`). # Campanhas > Campanhas pelo WhatsApp Oficial (API da Meta) na API da TopTalking - listar templates prontos (GET /templates), simular o preço (POST /campaigns/quote), criar a campanha para aprovação (POST /campaigns), acompanhar, ver destinatários, pausar e retomar. **Campanhas** são disparos para muitos contatos de uma vez pelo **WhatsApp Oficial (API da Meta)**, usando **templates aprovados**. São o caminho certo para promoções, novidades e avisos em massa — ao contrário de [`POST /messages`](/referencia/mensagens/#enviar-mensagem), que usa o WhatsApp conectado por QR Code e serve para mensagens individuais. Toda campanha passa pela **revisão da equipe da TopTalking** antes de sair. Aprovada, ela começa a enviar sozinha. ## Antes de começar | Requisito | Onde resolver | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | Um número do **WhatsApp Oficial** conectado. Ele é diferente dos [canais](/referencia/canais/) conectados por QR Code e não aparece em `GET /channels`. | No painel, em **Campanhas**. Sem ele, `POST /campaigns` responde `422 channel_unavailable`. | | Pelo menos um **template pronto** (`ready: true`): aprovado pela TopTalking e pela Meta. | No painel, em **Campanhas → Templates**. Pela API você só lista os templates. | | **Opt-in** dos destinatários: só envie para quem aceitou receber mensagens da empresa. | Na sua base. Veja [Boas práticas no WhatsApp](/comecando/boas-praticas-whatsapp/). | ## Ciclo de vida ```text POST /campaigns │ ▼ ┌──────────────────┐ a revisão pediu ajustes ┌───────────────────┐ │ pending_approval │ ──────────────────────► │ changes_requested │ └──────────────────┘ └───────────────────┘ │ aprovada pela TopTalking ▼ ┌──────────┐ em ~1 min ┌─────────┐ terminou ┌───────────┐ │ approved │ ────────► │ running │ ───────► │ completed │ └──────────┘ └─────────┘ └───────────┘ │ │ ▲ │ POST .../pause │ │ POST .../resume ▼ ▼ │ ┌──────────────────────────┐ │ │ paused │ ──┘ └──────────────────────────┘ failed: o envio não pôde continuar ``` | `status` | Significado | | ------------------- | ---------------------------------------------------------------------------------------------------------- | | `pending_approval` | Criada e aguardando a revisão da TopTalking. É assim que toda campanha criada pela API nasce. | | `changes_requested` | A revisão pediu ajustes: leia `review_notes`, corrija e crie uma nova campanha. | | `approved` | Aprovada. **Começa a enviar sozinha em até \~1 minuto.** | | `running` | Enviando. Acompanhe em `totals`. | | `paused` | Pausada por você (ou pelo painel). Retome com [`POST /campaigns/{campaign_id}/resume`](#retomar-campanha). | | `completed` | Todos os destinatários foram processados. | | `failed` | O envio não pôde continuar. | | `draft` | Rascunho criado no painel e ainda não enviado para revisão. | Para ser avisado de cada mudança, assine o webhook [`campaign.status_updated`](/webhooks/eventos/#campaignstatus_updated). ## Regras * **Template pronto:** `template.name` (e `template.language`, padrão `pt_BR`) precisa ser de um template com `ready: true`. Template inexistente → `404 resource_not_found`; existente mas não pronto → `400 invalid_field` (`param: "template"`). * **Variáveis na ordem:** `variables` de cada destinatário preenche `{{1}}`, `{{2}}`… do corpo do template, **na ordem**, e precisa ter exatamente `variables_count` itens. Números são convertidos em texto. * **Botão de link com variável:** se o template tem um botão com URL variável (ex.: `https://site.com/checkout/{{1}}`), o valor do link vai **no fim** de `variables`, depois das variáveis do corpo. Mande só a parte que entra no lugar de `{{1}}` (ex.: `86`). Ele já está contado em `variables_count`. * **Até 5.000 destinatários por requisição** (corpo de até 1 MB). Para listas maiores, crie várias campanhas. * **Telefones repetidos** na mesma requisição recebem a mensagem uma vez só. * **`skip_already_sent: true`** pula quem já recebeu alguma campanha da organização. Se isso deixar a lista vazia, a resposta é `400 invalid_field` (`param: "recipients"`). * **Preço congelado:** o preço por mensagem (`pricing.unit_price`) é o da tabela da sua organização **no momento da criação** e não muda depois. `pricing.estimated_total` = preço × destinatários; `pricing.charged_total` = preço × mensagens já enviadas. Simule antes com [`POST /campaigns/quote`](#simular-pre%C3%A7o). * **Pausar e retomar:** só dá para pausar campanhas `approved` ou `running`, e retomar campanhas `paused`. Fora disso → `409 invalid_state`. ## Passo a passo 1. **Escolha um template pronto** com [`GET /templates?ready=true`](#listar-templates). Anote `name`, `language` e `variables_count` (quantos valores cada destinatário precisa). ```bash curl "https://platform.toptalking.com.br/v1/templates?ready=true" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` 2. **Simule o preço** com [`POST /campaigns/quote`](#simular-pre%C3%A7o), informando o template e quantos destinatários você terá. Nada é criado. ```bash curl https://platform.toptalking.com.br/v1/campaigns/quote \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template": { "name": "promo_black_friday", "language": "pt_BR" }, "recipients_count": 1500 }' ``` 3. **Crie a campanha** com [`POST /campaigns`](#criar-campanha). Envie `Idempotency-Key` para uma retentativa não criar outra campanha igual. A resposta `201` vem com `status: "pending_approval"`. ```bash curl https://platform.toptalking.com.br/v1/campaigns \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: black-friday-2026-lote-1" \ -d '{ "name": "Black Friday 2026", "template": { "name": "promo_black_friday", "language": "pt_BR" }, "recipients": [ { "phone": "5511999998888", "name": "Maria Souza", "variables": ["Maria", "30%"] }, { "phone": "5521987654321", "name": "João Pereira", "variables": ["João", "30%"] }, { "phone": "5531991234567", "name": "Ana Lima", "variables": ["Ana", "30%"] } ], "skip_already_sent": false }' ``` 4. **Acompanhe o status.** Assine o webhook [`campaign.status_updated`](/webhooks/eventos/#campaignstatus_updated) ou consulte [`GET /campaigns/{campaign_id}`](#buscar-campanha) de tempos em tempos (a cada 1–5 minutos é suficiente). Se vier `changes_requested`, leia `review_notes`. 5. **Veja quem recebeu** com [`GET /campaigns/{campaign_id}/recipients`](#destinat%C3%A1rios-da-campanha) — filtre por `status=failed` para ver as falhas e o motivo em `error`. ### Exemplo completo * JavaScript campanha.mjs ```js // Node 18+. Rode no seu servidor: node campanha.mjs const API = 'https://platform.toptalking.com.br/v1'; const headers = { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }; async function api(path, init = {}) { const res = await fetch(`${API}${path}`, { ...init, headers: { ...headers, ...init.headers } }); const body = await res.json(); if (!res.ok) throw new Error(`${res.status} ${body.error.code} (${body.error.param ?? '-'}): ${body.error.message}`); return body; } const clientes = [ { phone: '5511999998888', nome: 'Maria Souza' }, { phone: '5521987654321', nome: 'João Pereira' }, { phone: '5531991234567', nome: 'Ana Lima' }, ]; // 1. Template pronto const { data: templates } = await api('/templates?ready=true'); const template = templates.find((t) => t.name === 'promo_black_friday'); if (!template) throw new Error('Template não está pronto: confira no painel, em Campanhas → Templates.'); // 2. Preço const orcamento = await api('/campaigns/quote', { method: 'POST', body: JSON.stringify({ template: { name: template.name, language: template.language }, recipients_count: clientes.length, }), }); console.log(`Custo estimado: ${orcamento.currency} ${orcamento.total}`); // 3. Criar (as variables têm exatamente template.variables_count itens) const campanha = await api('/campaigns', { method: 'POST', headers: { 'Idempotency-Key': 'black-friday-2026-lote-1' }, body: JSON.stringify({ name: 'Black Friday 2026', template: { name: template.name, language: template.language }, recipients: clientes.map((c) => ({ phone: c.phone, name: c.nome, variables: [c.nome.split(' ')[0], '30%'], })), skip_already_sent: false, }), }); console.log('Campanha criada:', campanha.id, campanha.status); // pending_approval // 4. Acompanhar (ou use o webhook campaign.status_updated) const FINAIS = ['completed', 'failed', 'changes_requested']; let atual = campanha; while (!FINAIS.includes(atual.status)) { await new Promise((r) => setTimeout(r, 60_000)); // 1 minuto atual = await api(`/campaigns/${campanha.id}`); console.log(atual.status, atual.totals); } if (atual.status === 'changes_requested') console.log('Ajustes pedidos:', atual.review_notes); // 5. Falhas const { data: falhas } = await api(`/campaigns/${campanha.id}/recipients?status=failed&limit=100`); for (const f of falhas) console.log(f.phone, f.error); ``` * Python campanha.py ```python import os import time import requests API = "https://platform.toptalking.com.br/v1" HEADERS = {"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"} def api(method, path, **kwargs): res = requests.request(method, f"{API}{path}", headers={**HEADERS, **kwargs.pop("headers", {})}, timeout=60, **kwargs) body = res.json() if not res.ok: e = body["error"] raise RuntimeError(f"{res.status_code} {e['code']} ({e.get('param')}): {e['message']}") return body clientes = [ {"phone": "5511999998888", "nome": "Maria Souza"}, {"phone": "5521987654321", "nome": "João Pereira"}, {"phone": "5531991234567", "nome": "Ana Lima"}, ] # 1. Template pronto templates = api("GET", "/templates", params={"ready": "true"})["data"] template = next(t for t in templates if t["name"] == "promo_black_friday") ref = {"name": template["name"], "language": template["language"]} # 2. Preço orcamento = api("POST", "/campaigns/quote", json={"template": ref, "recipients_count": len(clientes)}) print("Custo estimado:", orcamento["currency"], orcamento["total"]) # 3. Criar campanha = api( "POST", "/campaigns", headers={"Idempotency-Key": "black-friday-2026-lote-1"}, json={ "name": "Black Friday 2026", "template": ref, "recipients": [ {"phone": c["phone"], "name": c["nome"], "variables": [c["nome"].split()[0], "30%"]} for c in clientes ], "skip_already_sent": False, }, ) print("Campanha criada:", campanha["id"], campanha["status"]) # 4. Acompanhar (ou use o webhook campaign.status_updated) while campanha["status"] not in ("completed", "failed", "changes_requested"): time.sleep(60) campanha = api("GET", f"/campaigns/{campanha['id']}") print(campanha["status"], campanha["totals"]) # 5. Falhas falhas = api("GET", f"/campaigns/{campanha['id']}/recipients", params={"status": "failed", "limit": 100}) for f in falhas["data"]: print(f["phone"], f["error"]) ``` ## O objeto template ```json { "id": "875269ea-ba67-436d-81b4-0d209df54520", "object": "template", "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING", "status": "APPROVED", "review_status": "approved", "ready": true, "body": "Oi {{1}}! A Black Friday da Loja Aurora chegou: {{2}} de desconto em todo o site até domingo. Responda SAIR para não receber mais.", "variables_count": 2, "header_type": "IMAGE", "rejected_reason": null, "created_at": "2026-10-20T14:00:00Z" } ``` | Campo | Tipo | Descrição | | ----------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------- | | `id` | UUID | ID do template. | | `object` | string | Sempre `template`. | | `name` | string | Nome do template. É o que você envia em `template.name`. | | `language` | string | Idioma (ex.: `pt_BR`). É o que você envia em `template.language`. | | `category` | string ou `null` | Categoria na Meta: `MARKETING`, `UTILITY` ou `AUTHENTICATION`. O preço por mensagem depende dela. | | `status` | string ou `null` | Status na Meta: `APPROVED`, `PENDING`, `REJECTED`, `PAUSED` ou `DISABLED`. | | `review_status` | string | Revisão interna da TopTalking: `approved`, `pending` ou `rejected`. | | `ready` | booleano | **`true` = pode ser usado em campanhas** (aprovado pela TopTalking e pela Meta). | | `body` | string ou `null` | Texto do corpo, com as variáveis `{{1}}`, `{{2}}`… | | `variables_count` | inteiro | Quantos itens cada destinatário precisa em `variables`: as variáveis do corpo mais 1 por botão de link com variável. | | `header_type` | string ou `null` | Tipo do cabeçalho: `TEXT`, `IMAGE`, `VIDEO`, `DOCUMENT` ou `null` (sem cabeçalho). | | `rejected_reason` | string ou `null` | Motivo da rejeição, quando houver. | | `created_at` | data | Criação. | ## Listar templates GET`/v1/templates`Testar Os templates de mensagem do WhatsApp Oficial da organização. Só os com `ready: true` podem ser usados em campanhas. Os templates são **criados e enviados para aprovação no painel**, em Campanhas → Templates. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --------- | ----- | -------- | ----------- | ------------------------------------------------ | | `ready` | query | booleano | não | `true` devolve só os templates prontos para uso. | A lista vem inteira numa página (`has_more: false`). * curl ```bash curl https://platform.toptalking.com.br/v1/templates \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/templates?ready=true', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: prontos } = await res.json(); for (const t of prontos) console.log(t.name, t.language, `${t.variables_count} variáveis`); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/templates", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"ready": "true"}, timeout=30, ) res.raise_for_status() prontos = res.json()["data"] ``` #### Resposta `200` Sem filtro, vêm também os templates que ainda não estão prontos: ```json { "object": "list", "data": [ { "id": "875269ea-ba67-436d-81b4-0d209df54520", "object": "template", "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING", "status": "APPROVED", "review_status": "approved", "ready": true, "body": "Oi {{1}}! A Black Friday da Loja Aurora chegou: {{2}} de desconto em todo o site até domingo. Responda SAIR para não receber mais.", "variables_count": 2, "header_type": "IMAGE", "rejected_reason": null, "created_at": "2026-10-20T14:00:00Z" }, { "id": "3acdaef8-6224-43e8-aa29-9de336a75b7b", "object": "template", "name": "lembrete_pagamento", "language": "pt_BR", "category": "UTILITY", "status": "PENDING", "review_status": "approved", "ready": false, "body": "Olá {{1}}, o boleto do pedido {{2}} vence em {{3}}. Precisa de ajuda? É só responder esta mensagem.", "variables_count": 3, "header_type": null, "rejected_reason": null, "created_at": "2026-11-10T19:12:00Z" } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## O objeto campanha ```json { "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "running", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 1, "sent": 2, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0.7 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": null } ``` | Campo | Tipo | Descrição | | ------------------------- | ---------------- | -------------------------------------------------------------------------------- | | `id` | UUID | ID da campanha. | | `object` | string | Sempre `campaign`. | | `name` | string | Nome da campanha. | | `status` | string | Veja o [ciclo de vida](#ciclo-de-vida). | | `review_notes` | string ou `null` | Observação da revisão da TopTalking (ex.: o que ajustar em `changes_requested`). | | `template` | objeto | `name`, `language` e `category` do template usado. | | `totals.recipients` | inteiro | Total de destinatários (= `pending` + `sent` + `failed`). | | `totals.pending` | inteiro | Ainda não enviados. | | `totals.sent` | inteiro | Enviados (**inclui** os entregues e os lidos). | | `totals.delivered` | inteiro | Entregues no celular (**inclui** os lidos). | | `totals.read` | inteiro | Lidos. | | `totals.failed` | inteiro | Falharam. | | `pricing.unit_price` | número | Preço por mensagem, **congelado na criação**. | | `pricing.currency` | string | Moeda (`BRL`). | | `pricing.estimated_total` | número | `unit_price` × destinatários. | | `pricing.charged_total` | número | `unit_price` × mensagens já enviadas (arredondado em 2 casas). | | `created_at` | data | Criação. | | `submitted_at` | data ou `null` | Quando foi enviada para revisão. | | `reviewed_at` | data ou `null` | Quando a TopTalking revisou. | | `started_at` | data ou `null` | Quando o envio começou. | | `completed_at` | data ou `null` | Quando terminou. | ## Simular preço POST`/v1/campaigns/quote`Testar Calcula quanto uma campanha vai custar, pela tabela de preço da sua organização, **sem criar nada**. Use antes de criar, para mostrar o valor a quem vai aprovar o disparo. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------------- | ----- | ------- | ----------- | ------------------------------------ | | `template.name` | corpo | string | sim | Nome do template. | | `template.language` | corpo | string | não | Idioma do template. Padrão: `pt_BR`. | | `recipients_count` | corpo | inteiro | sim | Quantos destinatários (1 a 100.000). | * curl ```bash curl https://platform.toptalking.com.br/v1/campaigns/quote \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template": { "name": "promo_black_friday", "language": "pt_BR" }, "recipients_count": 1500 }' ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/campaigns/quote', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ template: { name: 'promo_black_friday', language: 'pt_BR' }, recipients_count: 1500, }), }); const orcamento = await res.json(); console.log(`${orcamento.recipients} mensagens × R$ ${orcamento.unit_price} = R$ ${orcamento.total}`); ``` * Python ```python import os import requests res = requests.post( "https://platform.toptalking.com.br/v1/campaigns/quote", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={ "template": {"name": "promo_black_friday", "language": "pt_BR"}, "recipients_count": 1500, }, timeout=30, ) res.raise_for_status() orcamento = res.json() ``` #### Resposta `200` ```json { "object": "campaign_quote", "category": "MARKETING", "recipients": 1500, "unit_price": 0.35, "currency": "BRL", "total": 525 } ``` | Campo | Tipo | Descrição | | ------------ | ---------------- | --------------------------------------- | | `object` | string | Sempre `campaign_quote`. | | `category` | string ou `null` | Categoria do template (define o preço). | | `recipients` | inteiro | Quantidade simulada. | | `unit_price` | número | Preço por mensagem hoje. | | `currency` | string | Moeda (`BRL`). | | `total` | número | `unit_price` × `recipients`. | Esta é a única rota `POST` liberada para chaves **somente leitura**: ela só calcula, não cria nada. #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------------------- | --------------------------------------------------------------------------------- | | 400 | `missing_field`, `invalid_field` | Faltou `template` ou `recipients_count`, ou `recipients_count` fora de 1–100.000. | | 404 | `resource_not_found` | Não existe template com esse `name` e `language`. | ## Criar campanha POST`/v1/campaigns`Testar Cria um disparo pelo WhatsApp Oficial com um template pronto e **envia para a revisão da TopTalking**. A campanha nasce `pending_approval`; ao ser aprovada, o envio começa sozinho (`approved` → `running` → `completed`). O preço por mensagem fica congelado neste momento. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------------------ | --------- | ---------------- | ----------------------------- | -------------------------------------------------------------------------------------- | | `name` | corpo | string | sim | Nome da campanha (até 120 caracteres). | | `template.name` | corpo | string | sim | Nome de um template com `ready: true`. | | `template.language` | corpo | string | não | Idioma do template. Padrão: `pt_BR`. | | `recipients` | corpo | lista | sim | De 1 a 5.000 destinatários. | | `recipients[].phone` | corpo | string | sim | Telefone com DDI e DDD (ex.: `5511999998888`). | | `recipients[].name` | corpo | string | não | Nome do destinatário. | | `recipients[].variables` | corpo | lista de strings | se o template tiver variáveis | Valores de `{{1}}`, `{{2}}`… na ordem. Precisa ter exatamente `variables_count` itens. | | `skip_already_sent` | corpo | booleano | não | `true` pula quem já recebeu alguma campanha desta organização. Padrão: `false`. | | `Idempotency-Key` | cabeçalho | string | não (recomendado) | Evita criar a mesma campanha duas vezes numa retentativa. | * curl ```bash curl https://platform.toptalking.com.br/v1/campaigns \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: black-friday-2026-lote-1" \ -d '{ "name": "Black Friday 2026", "template": { "name": "promo_black_friday", "language": "pt_BR" }, "recipients": [ { "phone": "5511999998888", "name": "Maria Souza", "variables": ["Maria", "30%"] }, { "phone": "5521987654321", "name": "João Pereira", "variables": ["João", "30%"] }, { "phone": "5531991234567", "name": "Ana Lima", "variables": ["Ana", "30%"] } ], "skip_already_sent": false }' ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/campaigns', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'black-friday-2026-lote-1', }, body: JSON.stringify({ name: 'Black Friday 2026', template: { name: 'promo_black_friday', language: 'pt_BR' }, recipients: [ { phone: '5511999998888', name: 'Maria Souza', variables: ['Maria', '30%'] }, { phone: '5521987654321', name: 'João Pereira', variables: ['João', '30%'] }, { phone: '5531991234567', name: 'Ana Lima', variables: ['Ana', '30%'] }, ], skip_already_sent: false, }), }); const campanha = await res.json(); if (!res.ok) throw new Error(`${campanha.error.code} (${campanha.error.param}): ${campanha.error.message}`); console.log(campanha.id, campanha.status); // "pending_approval" ``` * Python ```python import os import requests res = requests.post( "https://platform.toptalking.com.br/v1/campaigns", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": "black-friday-2026-lote-1", }, json={ "name": "Black Friday 2026", "template": {"name": "promo_black_friday", "language": "pt_BR"}, "recipients": [ {"phone": "5511999998888", "name": "Maria Souza", "variables": ["Maria", "30%"]}, {"phone": "5521987654321", "name": "João Pereira", "variables": ["João", "30%"]}, {"phone": "5531991234567", "name": "Ana Lima", "variables": ["Ana", "30%"]}, ], "skip_already_sent": False, }, timeout=60, ) campanha = res.json() if not res.ok: erro = campanha["error"] raise RuntimeError(f"{erro['code']} ({erro.get('param')}): {erro['message']}") ``` #### Resposta `201` ```json { "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "pending_approval", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 3, "sent": 0, "delivered": 0, "read": 0, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": null, "started_at": null, "completed_at": null } ``` #### Erros | HTTP | `code` | `param` | Quando acontece | | ---- | ---------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------- | | 400 | `missing_field` | `name`, `template` ou `recipients` | Faltou um campo obrigatório. | | 400 | `invalid_field` | `template` | O template existe, mas não está pronto (`ready: false`). | | 400 | `invalid_field` | `recipients[3].variables` | A quantidade de `variables` desse destinatário é diferente de `variables_count`. | | 400 | `invalid_field` | `recipients` | Mais de 5.000 destinatários, lista vazia ou, com `skip_already_sent: true`, todos já receberam alguma campanha. | | 400 | `invalid_phone` | `recipients[n].phone` | Telefone inválido em um destinatário. | | 403 | `read_only_key` | — | Chave somente leitura. | | 404 | `resource_not_found` | `template` | Não existe template com esse `name` e `language`. | | 409 | `idempotency_conflict` | — | `Idempotency-Key` reutilizada com outro corpo. | | 422 | `channel_unavailable` | — | A organização não tem número do WhatsApp Oficial conectado. | 400 invalid_field (variáveis) ```json { "error": { "type": "invalid_request_error", "code": "invalid_field", "message": "O template \"promo_black_friday\" tem 2 variáveis; `recipients[3].variables` trouxe 1.", "param": "recipients[3].variables", "request_id": "req_6f1d2c3b4a5e" } } ``` 422 channel_unavailable ```json { "error": { "type": "channel_error", "code": "channel_unavailable", "message": "Nenhum número do WhatsApp Oficial conectado. Conecte no painel em Campanhas.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Listar campanhas GET`/v1/campaigns`Testar As campanhas da organização, **das mais recentes para as mais antigas**, com [paginação](/comecando/paginacao/). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --------- | ----- | ------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `status` | query | string | não | Só campanhas neste status (`draft`, `pending_approval`, `approved`, `changes_requested`, `running`, `paused`, `completed` ou `failed`). | | `limit` | query | inteiro | não | Itens por página, 1 a 100 (padrão 20). | | `cursor` | query | string | não | `next_cursor` da página anterior. | * curl ```bash curl "https://platform.toptalking.com.br/v1/campaigns?limit=10" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/campaigns?status=running', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: enviando } = await res.json(); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/campaigns", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"limit": 10}, timeout=30, ) res.raise_for_status() campanhas = res.json()["data"] ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "running", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 1, "sent": 2, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0.7 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": null }, { "id": "4815f77a-bd81-4e28-bded-100b6a109cad", "object": "campaign", "name": "Aviso de feriado - Finados", "status": "completed", "review_notes": null, "template": { "name": "aviso_horario_feriado", "language": "pt_BR", "category": "UTILITY" }, "totals": { "recipients": 1500, "pending": 0, "sent": 1480, "delivered": 1420, "read": 900, "failed": 20 }, "pricing": { "unit_price": 0.08, "currency": "BRL", "estimated_total": 120, "charged_total": 118.4 }, "created_at": "2026-10-30T13:10:00Z", "submitted_at": "2026-10-30T13:10:00Z", "reviewed_at": "2026-10-30T14:02:00Z", "started_at": "2026-10-30T14:02:55Z", "completed_at": "2026-10-30T14:41:18Z" } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## Buscar campanha GET`/v1/campaigns/{campaign_id}`Testar Uma campanha pelo ID, com status, contadores e preço atualizados. Use para acompanhar o envio quando não estiver usando o webhook [`campaign.status_updated`](/webhooks/eventos/#campaignstatus_updated). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------- | ------- | ---- | ----------- | --------------- | | `campaign_id` | caminho | UUID | sim | ID da campanha. | * curl ```bash curl https://platform.toptalking.com.br/v1/campaigns/f2143187-f4f8-43b0-9c03-57a26c575f41 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const campaignId = 'f2143187-f4f8-43b0-9c03-57a26c575f41'; const res = await fetch(`https://platform.toptalking.com.br/v1/campaigns/${campaignId}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const campanha = await res.json(); const { recipients, pending } = campanha.totals; console.log(`${campanha.status}: ${recipients - pending}/${recipients} processados`); ``` * Python ```python import os import requests campaign_id = "f2143187-f4f8-43b0-9c03-57a26c575f41" res = requests.get( f"https://platform.toptalking.com.br/v1/campaigns/{campaign_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() campanha = res.json() ``` #### Resposta `200` ```json { "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "completed", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 0, "sent": 3, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 1.05 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": "2026-11-11T16:21:30Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ------------------------------------------------ | | 404 | `resource_not_found` | A campanha não existe ou é de outra organização. | ## Destinatários da campanha GET`/v1/campaigns/{campaign_id}/recipients`Testar O status de envio de cada destinatário, **na ordem em que foram enviados à API**, com [paginação](/comecando/paginacao/). Filtre por `status=failed` para ver quem não recebeu e o motivo. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------- | ------- | ------- | ----------- | -------------------------------------------------------------- | | `campaign_id` | caminho | UUID | sim | ID da campanha. | | `status` | query | string | não | `pending`, `sending`, `sent`, `delivered`, `read` ou `failed`. | | `limit` | query | inteiro | não | Itens por página, 1 a 100 (padrão 20). | | `cursor` | query | string | não | `next_cursor` da página anterior. | * curl ```bash curl "https://platform.toptalking.com.br/v1/campaigns/f2143187-f4f8-43b0-9c03-57a26c575f41/recipients?limit=100" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js // Percorre todas as páginas de destinatários que falharam const API = 'https://platform.toptalking.com.br/v1'; const headers = { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }; const campaignId = 'f2143187-f4f8-43b0-9c03-57a26c575f41'; const falhas = []; let cursor = null; do { const url = new URL(`${API}/campaigns/${campaignId}/recipients`); url.searchParams.set('status', 'failed'); url.searchParams.set('limit', '100'); if (cursor) url.searchParams.set('cursor', cursor); const page = await (await fetch(url, { headers })).json(); falhas.push(...page.data); cursor = page.has_more ? page.next_cursor : null; } while (cursor); ``` * Python ```python import os import requests campaign_id = "f2143187-f4f8-43b0-9c03-57a26c575f41" res = requests.get( f"https://platform.toptalking.com.br/v1/campaigns/{campaign_id}/recipients", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"limit": 100}, timeout=30, ) res.raise_for_status() for d in res.json()["data"]: print(d["phone"], d["status"], d["error"] or "") ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "434c30a3-7853-4b6f-9b21-e02da711774a", "object": "campaign_recipient", "phone": "5511999998888", "name": "Maria Souza", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "variables": ["Maria", "30%"], "status": "read", "error": null, "sent_at": "2026-11-11T16:20:43Z", "delivered_at": "2026-11-11T16:20:45Z", "read_at": "2026-11-11T16:24:10Z" }, { "id": "5c80c4da-1858-41ca-8969-746ea12fdc01", "object": "campaign_recipient", "phone": "5521987654321", "name": "João Pereira", "contact_id": "721bfe94-e549-4354-b90c-a8d679e9b271", "variables": ["João", "30%"], "status": "delivered", "error": null, "sent_at": "2026-11-11T16:20:44Z", "delivered_at": "2026-11-11T16:20:47Z", "read_at": null }, { "id": "a1138591-7dd7-45cc-8a72-9f492f087c77", "object": "campaign_recipient", "phone": "5531991234567", "name": "Ana Lima", "contact_id": "6586daec-7197-478e-b0ff-5b6659c11d26", "variables": ["Ana", "30%"], "status": "sent", "error": null, "sent_at": "2026-11-11T16:21:29Z", "delivered_at": null, "read_at": null } ], "has_more": false, "next_cursor": null } ``` | Campo | Tipo | Descrição | | -------------- | ---------------- | -------------------------------------------------------------- | | `id` | UUID | ID do destinatário na campanha. | | `object` | string | Sempre `campaign_recipient`. | | `phone` | string | Telefone, só dígitos com DDI. | | `name` | string ou `null` | Nome enviado na criação. | | `contact_id` | UUID ou `null` | Contato (conversa) correspondente no painel, quando houver. | | `variables` | lista de strings | Valores usados nas variáveis do template. | | `status` | string | `pending`, `sending`, `sent`, `delivered`, `read` ou `failed`. | | `error` | string ou `null` | Motivo da falha, quando `status` é `failed`. | | `sent_at` | data ou `null` | Quando foi enviado. | | `delivered_at` | data ou `null` | Quando chegou no celular. | | `read_at` | data ou `null` | Quando foi lido. | #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ------------------------------------------------ | | 404 | `resource_not_found` | A campanha não existe ou é de outra organização. | ## Pausar campanha POST`/v1/campaigns/{campaign_id}/pause`Testar Interrompe o envio de uma campanha `approved` ou `running`. Quem já recebeu não é afetado; os pendentes esperam até você [retomar](#retomar-campanha). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------- | ------- | ---- | ----------- | --------------- | | `campaign_id` | caminho | UUID | sim | ID da campanha. | * curl ```bash curl -X POST https://platform.toptalking.com.br/v1/campaigns/f2143187-f4f8-43b0-9c03-57a26c575f41/pause \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const campaignId = 'f2143187-f4f8-43b0-9c03-57a26c575f41'; const res = await fetch(`https://platform.toptalking.com.br/v1/campaigns/${campaignId}/pause`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const campanha = await res.json(); if (res.status === 409) console.log('Não dá para pausar agora:', campanha.error.message); ``` * Python ```python import os import requests campaign_id = "f2143187-f4f8-43b0-9c03-57a26c575f41" res = requests.post( f"https://platform.toptalking.com.br/v1/campaigns/{campaign_id}/pause", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) campanha = res.json() ``` #### Resposta `200` ```json { "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "paused", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 1, "sent": 2, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0.7 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": null } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------------------------------------------------------------------------------- | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | A campanha não existe ou é de outra organização. | | 409 | `invalid_state` | A campanha não está `approved` nem `running` (ex.: ainda em revisão, já pausada ou concluída). | 409 invalid_state ```json { "error": { "type": "conflict_error", "code": "invalid_state", "message": "Só é possível pausar campanhas aprovadas ou em envio (status atual: pending_approval).", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Retomar campanha POST`/v1/campaigns/{campaign_id}/resume`Testar Volta a enviar uma campanha `paused`, **continuando de onde parou** (só os pendentes recebem). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------- | ------- | ---- | ----------- | --------------- | | `campaign_id` | caminho | UUID | sim | ID da campanha. | * curl ```bash curl -X POST https://platform.toptalking.com.br/v1/campaigns/f2143187-f4f8-43b0-9c03-57a26c575f41/resume \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const campaignId = 'f2143187-f4f8-43b0-9c03-57a26c575f41'; const res = await fetch(`https://platform.toptalking.com.br/v1/campaigns/${campaignId}/resume`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const campanha = await res.json(); // campanha.status === "running" ``` * Python ```python import os import requests campaign_id = "f2143187-f4f8-43b0-9c03-57a26c575f41" res = requests.post( f"https://platform.toptalking.com.br/v1/campaigns/{campaign_id}/resume", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) campanha = res.json() ``` #### Resposta `200` A campanha, de volta ao envio: ```json { "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "running", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 1, "sent": 2, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0.7 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": null } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ------------------------------------------------ | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | A campanha não existe ou é de outra organização. | | 409 | `invalid_state` | A campanha não está `paused`. | # Webhooks (gestão) > Gerencie webhooks pela API da TopTalking - listar, criar (com o secret de assinatura), buscar, atualizar, reativar, excluir, enviar evento de teste (ping) e ver o histórico de entregas. Estes endpoints gerenciam os **endereços** que recebem eventos. Para entender os eventos em si, como validar a assinatura e como funcionam as retentativas, veja a seção [Webhooks](/webhooks/visao-geral/). Você também pode fazer tudo isso no painel, em [Configurações → Desenvolvedor → Webhooks](https://toptalking.com.br/configuracoes/desenvolvedor). ## O objeto webhook ```json { "id": "f03b0773-5912-4a45-9139-c29375485843", "object": "webhook_endpoint", "url": "https://meusistema.com.br/webhooks/toptalking", "description": "CRM interno", "events": ["message.received", "contact.stage_changed"], "enabled": true, "disabled_reason": null, "last_delivery_at": "2026-11-11T14:12:41Z", "last_delivery_status": 200, "created_at": "2026-11-01T10:00:00Z", "updated_at": "2026-11-01T10:00:00Z" } ``` | Campo | Tipo | Descrição | | ---------------------- | ----------------- | ----------------------------------------------------------------------- | | `id` | UUID | ID do webhook. | | `object` | string | Sempre `webhook_endpoint`. | | `url` | string | Endereço HTTPS que recebe os eventos. | | `description` | string ou `null` | Descrição livre. | | `events` | lista | Eventos assinados, ou `["*"]` para todos. | | `enabled` | booleano | Se está recebendo eventos. | | `disabled_reason` | string ou `null` | Preenchido quando a TopTalking desativou o webhook por falhas seguidas. | | `last_delivery_at` | data ou `null` | Última tentativa de entrega. | | `last_delivery_status` | inteiro ou `null` | Status HTTP que o seu sistema respondeu na última entrega. | | `created_at` | data | Criação. | | `updated_at` | data | Última alteração. | ### Eventos disponíveis Valores aceitos em `events`: | Valor | Quando chega | | -------------------------- | ---------------------------------------------------------------------------------------- | | `*` | Todos os eventos. | | `message.received` | Um contato mandou mensagem. | | `message.sent` | Uma mensagem saiu pela conta. | | `message.status_updated` | O status de entrega de uma mensagem enviada mudou. | | `contact.created` | Um contato novo entrou na base. | | `contact.updated` | Mudou nome, e-mail, etapa, responsável, IA, arquivamento ou finalização. | | `contact.stage_changed` | O card mudou de etapa no Kanban. | | `scheduled_message.sent` | Uma mensagem agendada foi enviada. | | `scheduled_message.failed` | Uma mensagem agendada falhou. | | `channel.status_updated` | Um WhatsApp conectou, caiu ou foi desconectado. | | `campaign.status_updated` | Uma campanha mudou de status (aprovada, ajustes pedidos, enviando, pausada, concluída…). | O evento de teste `ping` não precisa ser assinado: ele sempre é entregue quando você pede um teste. Os payloads de cada evento estão em [Eventos e payloads](/webhooks/eventos/). ## Listar webhooks GET`/v1/webhooks`Testar Todos os webhooks da organização (no máximo 10). Use para conferir se o seu endereço já está cadastrado antes de criar outro. Não tem parâmetros. A lista vem inteira numa página (`has_more: false`). * curl ```bash curl https://platform.toptalking.com.br/v1/webhooks \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/webhooks', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { data: webhooks } = await res.json(); const desativados = webhooks.filter((w) => !w.enabled); ``` * Python ```python import os import requests res = requests.get( "https://platform.toptalking.com.br/v1/webhooks", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() webhooks = res.json()["data"] ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "f03b0773-5912-4a45-9139-c29375485843", "object": "webhook_endpoint", "url": "https://meusistema.com.br/webhooks/toptalking", "description": "CRM interno", "events": ["message.received", "contact.stage_changed"], "enabled": true, "disabled_reason": null, "last_delivery_at": "2026-11-11T14:12:41Z", "last_delivery_status": 200, "created_at": "2026-11-01T10:00:00Z", "updated_at": "2026-11-01T10:00:00Z" } ], "has_more": false, "next_cursor": null } ``` #### Erros Só os gerais: `401` (chave) e `429` (limite por minuto). Veja [Erros](/comecando/erros/). ## Criar webhook POST`/v1/webhooks`Testar Cadastra um endereço do seu sistema para receber eventos. A resposta traz o **`secret`** (só desta vez), usado para [validar a assinatura](/webhooks/assinatura/). Máximo de **10 webhooks por organização**. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ----------------- | --------- | -------- | ----------- | ------------------------------------------------------------------------ | | `url` | corpo | string | sim | Endereço **HTTPS público** do seu sistema. | | `events` | corpo | lista | não | [Eventos](#eventos-dispon%C3%ADveis) a receber. Padrão: `["*"]` (todos). | | `description` | corpo | string | não | Descrição (até 200 caracteres). | | `enabled` | corpo | booleano | não | Padrão: `true`. | | `Idempotency-Key` | cabeçalho | string | não | Evita criar dois webhooks numa retentativa. | * curl ```bash curl https://platform.toptalking.com.br/v1/webhooks \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: webhook-crm-interno" \ -d '{ "url": "https://meusistema.com.br/webhooks/toptalking", "events": ["message.received", "contact.stage_changed"], "description": "CRM interno" }' ``` * JavaScript ```js const res = await fetch('https://platform.toptalking.com.br/v1/webhooks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'webhook-crm-interno', }, body: JSON.stringify({ url: 'https://meusistema.com.br/webhooks/toptalking', events: ['message.received', 'contact.stage_changed'], description: 'CRM interno', }), }); const webhook = await res.json(); // Guarde webhook.secret AGORA (ex.: na variável TOPTALKING_WEBHOOK_SECRET). ``` * Python ```python import os import requests res = requests.post( "https://platform.toptalking.com.br/v1/webhooks", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": "webhook-crm-interno", }, json={ "url": "https://meusistema.com.br/webhooks/toptalking", "events": ["message.received", "contact.stage_changed"], "description": "CRM interno", }, timeout=30, ) res.raise_for_status() webhook = res.json() # Guarde webhook["secret"] AGORA (ex.: na variável TOPTALKING_WEBHOOK_SECRET). ``` #### Resposta `201` O webhook criado **com o `secret`**: ```json { "id": "f03b0773-5912-4a45-9139-c29375485843", "object": "webhook_endpoint", "url": "https://meusistema.com.br/webhooks/toptalking", "description": "CRM interno", "events": ["message.received", "contact.stage_changed"], "enabled": true, "disabled_reason": null, "last_delivery_at": null, "last_delivery_status": null, "created_at": "2026-11-01T10:00:00Z", "updated_at": "2026-11-01T10:00:00Z", "secret": "whsec_d8d10826ecb93b4679de7409553d10982e864a172f6df97b" } ``` O secret só vem nesta resposta A API não devolve o `secret` de novo. Se perder, revele ou gere um novo no painel (**Revelar segredo** / **Gerar novo segredo**, no menu do webhook). #### Erros | HTTP | `code` | Quando acontece | | ---- | ---------------------- | ------------------------------------------------------------------------------------------- | | 400 | `missing_field` | Faltou `url`. | | 400 | `invalid_field` | URL sem HTTPS ou não pública (localhost, rede interna), ou evento desconhecido em `events`. | | 403 | `read_only_key` | Chave somente leitura. | | 409 | `idempotency_conflict` | `Idempotency-Key` reutilizada com outro corpo. | | 422 | `limit_reached` | A organização já tem 10 webhooks. | 422 limit_reached ```json { "error": { "type": "invalid_request_error", "code": "limit_reached", "message": "Limite de 10 webhooks por organização atingido.", "request_id": "req_6f1d2c3b4a5e" } } ``` ## Buscar webhook GET`/v1/webhooks/{webhook_id}`Testar Um webhook pelo ID, sem o `secret`. Útil para conferir `enabled`, `disabled_reason` e o resultado da última entrega. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | -------------- | | `webhook_id` | caminho | UUID | sim | ID do webhook. | * curl ```bash curl https://platform.toptalking.com.br/v1/webhooks/f03b0773-5912-4a45-9139-c29375485843 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const id = 'f03b0773-5912-4a45-9139-c29375485843'; const res = await fetch(`https://platform.toptalking.com.br/v1/webhooks/${id}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const webhook = await res.json(); if (!webhook.enabled) console.warn('Webhook desativado:', webhook.disabled_reason); ``` * Python ```python import os import requests webhook_id = "f03b0773-5912-4a45-9139-c29375485843" res = requests.get( f"https://platform.toptalking.com.br/v1/webhooks/{webhook_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() webhook = res.json() ``` #### Resposta `200` ```json { "id": "f03b0773-5912-4a45-9139-c29375485843", "object": "webhook_endpoint", "url": "https://meusistema.com.br/webhooks/toptalking", "description": "CRM interno", "events": ["message.received", "contact.stage_changed"], "enabled": true, "disabled_reason": null, "last_delivery_at": "2026-11-11T14:12:41Z", "last_delivery_status": 200, "created_at": "2026-11-01T10:00:00Z", "updated_at": "2026-11-01T10:00:00Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | --------------------- | | 404 | `resource_not_found` | O webhook não existe. | ## Atualizar webhook PATCH`/v1/webhooks/{webhook_id}`Testar Muda o endereço, os eventos ou a descrição, ou pausa/reativa o webhook — inclusive um que a TopTalking desativou por falhas seguidas. Envie só o que quer mudar. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------- | ------- | ---------------- | ----------- | --------------------------------------------- | | `webhook_id` | caminho | UUID | sim | ID do webhook. | | `url` | corpo | string | não | Novo endereço HTTPS. | | `events` | corpo | lista | não | Nova lista de eventos (substitui a anterior). | | `description` | corpo | string ou `null` | não | Nova descrição. | | `enabled` | corpo | booleano | não | `false` pausa; `true` reativa. | * curl ```bash # Reativar e passar a receber também os eventos de conexão curl -X PATCH https://platform.toptalking.com.br/v1/webhooks/f03b0773-5912-4a45-9139-c29375485843 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "enabled": true, "events": ["message.received", "contact.stage_changed", "channel.status_updated"] }' ``` * JavaScript ```js const id = 'f03b0773-5912-4a45-9139-c29375485843'; const res = await fetch(`https://platform.toptalking.com.br/v1/webhooks/${id}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ enabled: true, events: ['message.received', 'contact.stage_changed', 'channel.status_updated'], }), }); const webhook = await res.json(); ``` * Python ```python import os import requests webhook_id = "f03b0773-5912-4a45-9139-c29375485843" res = requests.patch( f"https://platform.toptalking.com.br/v1/webhooks/{webhook_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={ "enabled": True, "events": ["message.received", "contact.stage_changed", "channel.status_updated"], }, timeout=30, ) res.raise_for_status() webhook = res.json() ``` #### Resposta `200` ```json { "id": "f03b0773-5912-4a45-9139-c29375485843", "object": "webhook_endpoint", "url": "https://meusistema.com.br/webhooks/toptalking", "description": "CRM interno", "events": ["message.received", "contact.stage_changed", "channel.status_updated"], "enabled": true, "disabled_reason": null, "last_delivery_at": "2026-11-11T14:12:41Z", "last_delivery_status": 200, "created_at": "2026-11-01T10:00:00Z", "updated_at": "2026-11-11T15:05:30Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | ------------------------------- | ----------------------------------------------------- | | 400 | `missing_field` | O corpo não tem nenhum campo para atualizar. | | 400 | `invalid_json`, `invalid_field` | Corpo inválido, URL sem HTTPS ou evento desconhecido. | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O webhook não existe. | ## Excluir webhook DELETE`/v1/webhooks/{webhook_id}`Testar Remove o webhook: o endereço para de receber eventos imediatamente. Para só pausar, use [`PATCH`](#atualizar-webhook) com `enabled: false`. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | -------------- | | `webhook_id` | caminho | UUID | sim | ID do webhook. | * curl ```bash curl -X DELETE https://platform.toptalking.com.br/v1/webhooks/f03b0773-5912-4a45-9139-c29375485843 \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const id = 'f03b0773-5912-4a45-9139-c29375485843'; const res = await fetch(`https://platform.toptalking.com.br/v1/webhooks/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const { deleted } = await res.json(); // true ``` * Python ```python import os import requests webhook_id = "f03b0773-5912-4a45-9139-c29375485843" res = requests.delete( f"https://platform.toptalking.com.br/v1/webhooks/{webhook_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) res.raise_for_status() ``` #### Resposta `200` ```json { "id": "f03b0773-5912-4a45-9139-c29375485843", "object": "webhook_endpoint", "deleted": true } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------- | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O webhook não existe. | ## Enviar evento de teste POST`/v1/webhooks/{webhook_id}/test`Testar Coloca na fila um evento [`ping`](/webhooks/eventos/#ping) para o endereço — **mesmo se o webhook estiver desativado**. É o mesmo que o botão **Enviar teste** do painel. Use para conferir se o seu endpoint recebe e valida a assinatura. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ---- | ----------- | -------------- | | `webhook_id` | caminho | UUID | sim | ID do webhook. | * curl ```bash curl -X POST https://platform.toptalking.com.br/v1/webhooks/f03b0773-5912-4a45-9139-c29375485843/test \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const id = 'f03b0773-5912-4a45-9139-c29375485843'; const res = await fetch(`https://platform.toptalking.com.br/v1/webhooks/${id}/test`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` }, }); const entrega = await res.json(); // status 202 ``` * Python ```python import os import requests webhook_id = "f03b0773-5912-4a45-9139-c29375485843" res = requests.post( f"https://platform.toptalking.com.br/v1/webhooks/{webhook_id}/test", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30, ) entrega = res.json() # res.status_code == 202 ``` #### Resposta `202` A entrega criada, ainda na fila: ```json { "id": "d94d8ee3-4eb6-41f1-8c44-44bd0907d99f", "object": "webhook_delivery", "webhook_id": "f03b0773-5912-4a45-9139-c29375485843", "event_id": "252a2c18-7b60-47a6-9feb-9c8322f2ba50", "event_type": "ping", "status": "pending", "attempts": 0, "response_status": null, "error": null, "duration_ms": null, "next_attempt_at": "2026-11-11T15:10:00Z", "delivered_at": null, "created_at": "2026-11-11T15:10:00Z" } ``` #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | ---------------------- | | 403 | `read_only_key` | Chave somente leitura. | | 404 | `resource_not_found` | O webhook não existe. | ## Histórico de entregas GET`/v1/webhooks/{webhook_id}/deliveries`Testar As últimas tentativas de entrega deste webhook, com [paginação](/comecando/paginacao/). Os eventos ficam guardados por **30 dias**. Use para investigar por que o seu sistema não recebeu um evento. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | ------------ | ------- | ------- | ----------- | -------------------------------------- | | `webhook_id` | caminho | UUID | sim | ID do webhook. | | `limit` | query | inteiro | não | Itens por página, 1 a 100 (padrão 20). | | `cursor` | query | string | não | `next_cursor` da página anterior. | * curl ```bash curl "https://platform.toptalking.com.br/v1/webhooks/f03b0773-5912-4a45-9139-c29375485843/deliveries?limit=20" \ -H "Authorization: Bearer $TOPTALKING_API_KEY" ``` * JavaScript ```js const id = 'f03b0773-5912-4a45-9139-c29375485843'; const res = await fetch( `https://platform.toptalking.com.br/v1/webhooks/${id}/deliveries?limit=20`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` } }, ); const { data: entregas } = await res.json(); const comFalha = entregas.filter((e) => e.status !== 'succeeded'); ``` * Python ```python import os import requests webhook_id = "f03b0773-5912-4a45-9139-c29375485843" res = requests.get( f"https://platform.toptalking.com.br/v1/webhooks/{webhook_id}/deliveries", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"limit": 20}, timeout=30, ) res.raise_for_status() entregas = res.json()["data"] ``` #### Resposta `200` ```json { "object": "list", "data": [ { "id": "7c8f5d3c-c721-44aa-a161-3fe74f6ad736", "object": "webhook_delivery", "webhook_id": "f03b0773-5912-4a45-9139-c29375485843", "event_id": "597b7d23-208d-4b5b-82a2-fb1703a81d63", "event_type": "message.received", "status": "pending", "attempts": 2, "response_status": 500, "error": "HTTP 500", "duration_ms": 1240, "next_attempt_at": "2026-11-11T14:18:42Z", "delivered_at": null, "created_at": "2026-11-11T14:12:41Z" }, { "id": "d94d8ee3-4eb6-41f1-8c44-44bd0907d99f", "object": "webhook_delivery", "webhook_id": "f03b0773-5912-4a45-9139-c29375485843", "event_id": "252a2c18-7b60-47a6-9feb-9c8322f2ba50", "event_type": "ping", "status": "succeeded", "attempts": 1, "response_status": 200, "error": null, "duration_ms": 184, "next_attempt_at": null, "delivered_at": "2026-11-11T15:10:01Z", "created_at": "2026-11-11T15:10:00Z" } ], "has_more": false, "next_cursor": null } ``` | Campo | Tipo | Descrição | | ----------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `id` | UUID | ID da entrega (o mesmo do cabeçalho `X-TopTalking-Delivery-Id`). | | `object` | string | Sempre `webhook_delivery`. | | `webhook_id` | UUID | Webhook de destino. | | `event_id` | UUID | ID do evento (o mesmo em todas as tentativas). | | `event_type` | string | Tipo do evento (ex.: `message.received`). | | `status` | string | `pending` (na fila ou esperando nova tentativa), `processing` (enviando), `succeeded` (seu sistema respondeu 2xx) ou `failed`. | | `attempts` | inteiro | Quantas tentativas já foram feitas. | | `response_status` | inteiro ou `null` | Status HTTP que o seu sistema respondeu na última tentativa. | | `error` | string ou `null` | Motivo da falha (timeout, erro de conexão, status não 2xx…). | | `duration_ms` | inteiro ou `null` | Quanto o seu sistema demorou para responder. | | `next_attempt_at` | data ou `null` | Quando será a próxima tentativa, se houver. | | `delivered_at` | data ou `null` | Quando foi entregue com sucesso. | | `created_at` | data | Quando a entrega foi criada. | Entregas que falharam podem ser reenviadas pelo painel (botão **Reenviar**, em **Ver entregas**). Veja [Entregas e novas tentativas](/webhooks/entregas/). #### Erros | HTTP | `code` | Quando acontece | | ---- | -------------------- | --------------------- | | 404 | `resource_not_found` | O webhook não existe. |