Agent Bots para agentes de IA
Como um agente de IA externo opera o NooviChat: autentica como um Agent Bot, lê o histórico da conversa, responde, movimenta o CRM, agenda e programa follow-up. Esta página descreve a superfície exata que um token de bot alcança, a permissão que cada rota exige e o corpo de resposta de cada recusa.
Modelo de acesso
Um Agent Bot é um ator não-humano com token próprio. Ele não é um usuário, não ocupa assento, não aparece em listagem de agentes e não pode ser atribuído como responsável de card. O que ele alcança é decidido por três travas independentes, avaliadas nesta ordem. Falhar em qualquer uma encerra a requisição.
| Trava | Pergunta | Falha responde |
|---|---|---|
| 1. Superfície | A rota e a ação estão na lista de superfície de bot? | 401 |
| 2. Permissão | O bot carrega ao menos uma das permissões exigidas por aquela superfície? | 401 |
| 3. Vínculo de conta | O bot está ligado, por vínculo de inbox ativo, à conta do path? | 404 |
Permissão sem vínculo não abre nada
As travas 2 e 3 são independentes. Um bot com pipeline_manage mas sem nenhum vínculo de inbox ativo naquela conta recebe 404 em toda rota da conta — inclusive nas que a permissão dele cobre. E um bot vinculado a uma inbox, porém sem permissão nenhuma, recebe 401 em toda rota. Não existe caminho em que uma compense a outra.
O padrão é não poder nada
A lista de permissões de um bot recém-criado é vazia. Enquanto um administrador da conta não gravar permissões nele, toda chamada autenticada com o token desse bot responde 401 com {"error":"Access to this endpoint is not authorized for bots"}. Atualizar a versão do NooviChat não concede permissão a bot nenhum.
Um bot serve uma conta só
Um Agent Bot pode estar vinculado a várias inboxes, mas todas precisam pertencer à mesma conta. Tentar salvar vínculos em contas diferentes é recusado na gravação. Um agente de IA que atende vários clientes precisa de um bot e um token por conta — não existe token multi-conta.
Autenticação
O token do bot vai no header api_access_token. O NooviChat não lê Authorization: Bearer nesta API: uma requisição que use apenas Authorization é tratada como não autenticada e cai no fluxo de login de usuário, não no de bot.
curl -s "https://chat.seudominio.com/api/v1/accounts/1/conversations" \
-H "api_access_token: SEU_TOKEN_DE_BOT" \
-H "Content-Type: application/json"Header
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
api_access_token(header) | string | Sim | Token do Agent Bot. Identifica o bot e a conta que ele serve. |
Content-Type(header) | string | Nao | application/json nas requisições com corpo JSON. |
Como o agente descobre o account_id
O account_id não vem do token: ele faz parte do path de toda rota. O agente precisa recebê-lo na configuração. Se chamar com o id de outra conta, a resposta é 404 com {"error":"Resource could not be found"} — a mesma resposta de uma conta inexistente, de propósito, para não revelar quais contas existem.
Descobrir o que você pode
Chame isto antesde planejar uma tarefa. É a única rota liberada a qualquer agent-bot, inclusive um que ainda não tem permissão nenhuma — justamente para que a resposta a "o que eu posso fazer?" nunca seja a mesma negação genérica das outras rotas.
/api/v1/accounts/{account_id}/agent_bots/meDevolve as permissões concedidas, as caixas de entrada vinculadas e a lista de superfícies com o campo granted.
{
"id": 118,
"name": "Hermes Teste",
"account_id": 1,
"permissions": ["conversation_manage", "pipeline_manage", "appointment_manage"],
"inboxes": [{ "id": 18499, "name": "Vendas Teste" }],
"endpoints": [
{
"surface": "api/v1/accounts/conversations",
"actions": ["index", "show", "filter", "search", "create", "update"],
"requires_any_of": ["conversation_manage", "conversation_unassigned_manage"],
"granted": true
}
]
}Use granted para planejar, não para adivinhar
granted já cruza as suas permissões com o que cada superfície exige. Se vier false, a tarefa não é possível com este token — o certo é dizer isso a quem pediu e nomear a permissão de requires_any_of, em vez de tentar e falhar. Lembre que inboxes também limita: permissão sem vínculo não abre nada.
Permissões do bot
As permissões de um bot usam o mesmo vocabulário das funções personalizadas de usuário, mais uma exclusiva de bot. Elas ficam em bot_config.permissions, um array de strings. Um valor fora deste catálogo faz a gravação falhar com 422.
| Permissão | O que libera |
|---|---|
| conversation_manage | Todas as conversas da conta |
| conversation_unassigned_manage | Conversas não atribuídas e as próprias |
| conversation_participating_manage | Conversas próprias ou em que participa |
| conversation_awaiting_reply_view | Ver a lista "Sem resposta" |
| contact_manage | CRUD de contatos |
| report_manage | Relatórios |
| knowledge_base_manage | Portais e artigos da central de ajuda |
| pipeline_manage | Pipeline: CRUD e automações |
| pipeline_view | Pipeline: somente leitura |
| follow_up_manage | Follow-up: templates e automações |
| appointment_manage | Atendimentos: ações privilegiadas e configuração |
| commercial_analysis_manage | Análise Comercial |
| conversation_private_notes_view | Ler notas internas no histórico da conversa (exclusiva de bot) |
report_manage, knowledge_base_manage e commercial_analysis_manage
São permissões válidas de gravar num bot, mas nenhuma superfície de bot as exige hoje. Conceder qualquer uma delas não abre rota alguma para o token do bot. Concedê-las não é erro — é apenas inerte.
Provisionar o bot
Os três passos abaixo exigem um token de administrador da conta. Um bot não consegue se autoprovisionar: as rotas de agent_bots e de inboxes não fazem parte da superfície de bot e respondem 401 para um token de bot.
/api/v1/accounts/{account_id}/agent_bots1. Cria o bot. O token de acesso é devolvido apenas para administradores.
curl -s -X POST "https://chat.seudominio.com/api/v1/accounts/1/agent_bots" \
-H "api_access_token: TOKEN_DE_ADMIN" \
-H "Content-Type: application/json" \
-d '{
"name": "Agente Hermes",
"description": "Agente de IA externo",
"outgoing_url": "https://meuagente.exemplo.com/hooks/noovichat"
}'{
"id": 7,
"name": "Agente Hermes",
"description": "Agente de IA externo",
"outgoing_url": "https://meuagente.exemplo.com/hooks/noovichat",
"bot_type": "webhook",
"bot_config": {},
"account_id": 1,
"access_token": "AbCdEf...",
"secret": "...",
"system_bot": false
}/api/v1/accounts/{account_id}/agent_bots/{id}2. Concede as permissões. bot_config é substituído inteiro — envie sempre a lista completa.
curl -s -X PATCH "https://chat.seudominio.com/api/v1/accounts/1/agent_bots/7" \
-H "api_access_token: TOKEN_DE_ADMIN" \
-H "Content-Type: application/json" \
-d '{
"bot_config": {
"permissions": [
"conversation_manage",
"contact_manage",
"pipeline_manage",
"follow_up_manage",
"appointment_manage"
]
}
}'{
"message": "unknown bot permissions: pipeline_admin",
"attributes": ["base"]
}/api/v1/accounts/{account_id}/inboxes/{inbox_id}/set_agent_bot3. Vincula o bot a uma inbox. É este vínculo que faz o bot servir a conta.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
agent_bot | integer | Sim | Id do bot. Enviar null remove o vínculo da inbox. |
curl -s -X POST "https://chat.seudominio.com/api/v1/accounts/1/inboxes/42/set_agent_bot" \
-H "api_access_token: TOKEN_DE_ADMIN" \
-H "Content-Type: application/json" \
-d '{"agent_bot": 7}'Uma inbox tem no máximo um bot
Vincular um segundo bot à mesma inbox substitui o anterior. Desativar ou remover o vínculo derruba o acesso do bot à conta inteira: a partir daí toda rota volta a responder 404, mesmo com o token e as permissões intactos.
Superfície completa
Esta é a lista integral do que um token de bot alcança. Qualquer rota ou ação fora dela responde 401 com {"error":"Access to this endpoint is not authorized for bots"}, independentemente das permissões gravadas. Quando a coluna de permissão lista mais de um valor, basta o bot carregar um deles.
| Superfície | Permissão que abre | Ações liberadas |
|---|---|---|
| Conversas | conversation_manage OU conversation_unassigned_manage OU conversation_participating_manage | index, show, filter, search, create, update, meta, toggle_status, toggle_priority, toggle_typing_status, custom_attributes, unread, update_last_seen, mute, unmute, attachments |
| Mensagens da conversa | conversation_manage OU conversation_unassigned_manage OU conversation_participating_manage | index, create |
| Atribuição de conversa | conversation_manage | create |
| Etiquetas da conversa | conversation_manage | index, create |
| Follow-ups da conversa | follow_up_manage | index, show, create, update, count |
| Contatos | contact_manage | index, show, search, create, update |
| Funis (pipelines) | pipeline_manage OU pipeline_view | index, show, stages, create, update |
| Cards do funil | pipeline_manage OU pipeline_view | index, show, create, update, move_to_stage, reorder, recalculate_score, update_qualification_checklist |
| Status do negócio | pipeline_manage | mark_won, mark_lost, reopen |
| Catálogo de produtos | pipeline_manage OU pipeline_view | index, show, create, update, performance |
| Oportunidades (razão financeiro) | pipeline_manage | index, create, void |
| Vínculo card ↔ conversa | pipeline_manage | create |
| Vínculo card ↔ contato | pipeline_manage | create |
| Atividades do funil | pipeline_manage OU pipeline_view | index, create, timeline |
| Anexos do card | pipeline_manage | index, create |
| Responsável do card | pipeline_manage | assign |
| Regras de follow-up do funil | follow_up_manage OU pipeline_manage | index, show, create, update |
| Agendamentos | appointment_manage | index, show, create, update, confirm, complete, no_show, availability, availability_range, available_professionals, clients |
| Profissionais | appointment_manage | index, show, availability |
| Serviços | appointment_manage | index, show |
Apagar está fora da superfície
Nenhuma ação destroy está liberada para bot. Apagar conversa, contato, card, agendamento, follow-up, anexo ou regra responde 401. Não há parâmetro, header ou permissão que habilite isso. Se o seu fluxo depende de remover um registro, ele precisa de um usuário humano ou de um token de administrador.
Cancelar e reenviar follow-up também estão fora
As ações cancel e retry_send de follow-up não pertencem à superfície de bot: respondem 401, inclusive para um follow-up que o próprio bot criou. Além disso, um follow-up com autoria de bot só pode ser cancelado ou reenviado por um administrador da conta — nem o usuário dono da conversa consegue.
Contrato de erros
Os corpos abaixo são literais. As mensagens marcadas como traduzidas seguem o idioma configurado na conta; o status HTTP e as chaves JSON não mudam.
| Status | Corpo | Quando |
|---|---|---|
| 401 | {"error":"Invalid Access Token"} | O valor do header api_access_token não corresponde a nenhum token. Não há retry útil. |
| 401 | {"error":"Access to this endpoint is not authorized for bots"} | A rota não está na superfície de bot, OU a ação não está liberada naquela superfície, OU o bot não carrega nenhuma das permissões exigidas por ela. Corrigir exige conceder a permissão no bot — não adianta repetir a chamada. |
| 401 | {"error":"You are not authorized to do this action"} | A rota está na superfície e a permissão existe, mas a policy negou o registro específico (ex.: card fora de um funil que o bot alcança). Vale para as rotas fora do namespace /pipeline/. |
| 403 | {"error":"forbidden"} | Negação de policy nas rotas de Agendamento (appointments). |
| 403 | {"error":"<mensagem no idioma da conta>"} | Negação de policy nas rotas do namespace /pipeline/ e nas regras de follow-up do funil. Em pt-BR: "Você não está autorizado a acessar esta conta". |
| 403 | {"error":"...","code":"pipeline_board_disabled"} | A conta não tem o módulo Pipeline habilitado. O campo code é estável; a mensagem é traduzida. |
| 403 | {"error":"Agent bots cannot mark pipeline cards as lost"} | move_to_stage apontando para uma etapa marcada como "perdido". Bots não fecham negócio como perdido por essa rota. |
| 403 | {"error":"API access is not enabled for this account"} | A conta está com o acesso por API desligado. Nenhuma permissão do bot contorna isso. |
| 404 | {"error":"Resource could not be found"} | A conta do path não existe, está suspensa, o account_id é malformado, OU o bot não serve aquela conta. Também responde assim quando o registro pedido está fora do recorte que o bot enxerga. Não distingue os casos de propósito. |
| 404 | {"error":"Pipeline card not found"} | Card inexistente ou em um funil que não listou este bot. Específico das rotas de status do negócio. |
| 422 | {"error":"<mensagem>"} | Payload rejeitado pela validação. Usado por mensagens, follow-ups e a maior parte das rotas de card. |
| 422 | {"errors":["<mensagem>", "..."]} | Payload rejeitado nas regras de follow-up do funil. Note a chave no plural e o array — não é o mesmo formato acima. |
| 422 | {"message":"<mensagem>","attributes":["<campo>"]} | Payload rejeitado por ActiveRecord::RecordInvalid (ex.: gravar bot_config com permissão desconhecida). |
Como um agente deve reagir a cada classe
401 e 403 são estados de configuração: repetir a chamada produz o mesmo resultado. Registre e pare. 404 pode ser configuração (conta errada, vínculo removido) ou recorte de visibilidade (o registro existe mas o bot não o alcança) — as duas não são distinguíveis pela resposta. 422 é o único caso em que reformular o payload tem chance de sucesso.
Atendimento
| Método | Rota | Permissão exigida |
|---|---|---|
| GET | /api/v1/accounts/{account_id}/conversations | conversation_manage | conversation_unassigned_manage | conversation_participating_manage |
| GET | /api/v1/accounts/{account_id}/conversations/meta | idem |
| GET | /api/v1/accounts/{account_id}/conversations/search?q= | idem |
| POST | /api/v1/accounts/{account_id}/conversations/filter | idem |
| GET | /api/v1/accounts/{account_id}/conversations/{display_id} | idem |
| POST | /api/v1/accounts/{account_id}/conversations | idem |
| PATCH | /api/v1/accounts/{account_id}/conversations/{display_id} | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/toggle_status | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/toggle_priority | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/toggle_typing_status | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/custom_attributes | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/unread | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/update_last_seen | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/mute | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/unmute | idem |
| GET | /api/v1/accounts/{account_id}/conversations/{display_id}/attachments | idem |
| GET | /api/v1/accounts/{account_id}/conversations/{display_id}/messages | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/messages | idem |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/assignments | conversation_manage |
| GET | /api/v1/accounts/{account_id}/conversations/{display_id}/labels | conversation_manage |
| POST | /api/v1/accounts/{account_id}/conversations/{display_id}/labels | conversation_manage |
| GET | /api/v1/accounts/{account_id}/contacts | contact_manage |
| GET | /api/v1/accounts/{account_id}/contacts/search?q= | contact_manage |
| GET | /api/v1/accounts/{account_id}/contacts/{id} | contact_manage |
| POST | /api/v1/accounts/{account_id}/contacts | contact_manage |
| PATCH | /api/v1/accounts/{account_id}/contacts/{id} | contact_manage |
Recorte de listagem
GET /conversations devolve apenas conversas das inboxes em que o bot tem vínculo ativo. Não existe modo administrador para bot: um bot ligado a uma inbox não enxerga a conta inteira. Já GET /conversations/{display_id} alcança qualquer conversa da conta que o bot serve — listar e abrir têm recortes diferentes.
Ler o histórico da conversa
/api/v1/accounts/{account_id}/conversations/{display_id}/messagesRetorna mensagens da conversa. O identificador no path é o display_id da conversa, não o id interno.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
after(query) | integer | Nao | Cursor. Devolve até 100 mensagens com id maior que o valor, em ordem cronológica crescente. |
before(query) | integer | Nao | Cursor. Devolve até 20 mensagens com id menor que o valor, em ordem cronológica crescente. |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/conversations/318/messages" \
-H "api_access_token: SEU_TOKEN_DE_BOT"{
"meta": {
"labels": ["lead-quente"],
"additional_attributes": {},
"contact": { "id": 91, "name": "Ana Souza", "phone_number": "+5511999990000", "type": "contact" },
"assignee": { "id": 4, "name": "Carlos", "type": "user" },
"agent_last_seen_at": 1771596000,
"assignee_last_seen_at": 1771596000
},
"payload": [
{
"id": 88120,
"content": "Bom dia, ainda da tempo de agendar?",
"message_type": 0,
"content_type": "text",
"private": false,
"created_at": 1771595880,
"conversation_id": 318,
"inbox_id": 42,
"sender": { "id": 91, "name": "Ana Souza", "type": "contact" },
"attachments": []
},
{
"id": 88121,
"content": "Bom dia, Ana! Da sim.",
"message_type": 1,
"content_type": "text",
"private": false,
"created_at": 1771595940,
"conversation_id": 318,
"inbox_id": 42,
"sender": { "id": 7, "name": "Agente Hermes", "type": "agent_bot" }
}
]
}Sem cursor, o agente vê só o fim da conversa
Uma chamada sem after nem before devolve as 20 mensagens mais recentes, e nada indica no corpo que existe mais histórico atrás. Um agente que chama uma única vez e conclui a partir daí está lendo um recorte, não a conversa. Para o histórico inteiro, pagine.
Varredura completa, do início ao fim: comece com after=0 e avance usando o maior id devolvido. Pare quando payload vier vazio ou com menos de 100 itens.
BASE="https://chat.seudominio.com/api/v1/accounts/1/conversations/318/messages"
CURSOR=0
while : ; do
PAGE=$(curl -s "$BASE?after=$CURSOR" -H "api_access_token: SEU_TOKEN_DE_BOT")
COUNT=$(echo "$PAGE" | jq '.payload | length')
[ "$COUNT" -eq 0 ] && break
echo "$PAGE" | jq -c '.payload[]'
CURSOR=$(echo "$PAGE" | jq '.payload[-1].id')
[ "$COUNT" -lt 100 ] && break
doneLimites por forma de paginação
after sozinho: até 100 por página. before sozinho: até 20. after e before juntos: até 1000 mensagens no intervalo [after, before). Sem cursor: 20. Estes tetos são fixos e não há parâmetro de tamanho de página.
Saber o que você ainda não viu
O meta da resposta declara a paginação. Confira sempre — o truncamento é invisível sem estes campos, e o modo after corta jogando fora as mensagens mais recentes: é exatamente o que esconde a mensagem que você acabou de enviar, levando o agente a concluir que a entrega falhou e reenviar ao cliente.
"meta": {
"total_count": 108, // total real da conversa
"returned_count": 100, // quantas vieram nesta pagina
"has_more_before": false, // ha mensagens ANTES da mais antiga desta pagina
"has_more_after": true, // ha mensagens DEPOIS da mais recente desta pagina
"oldest_id": 83390,
"newest_id": 83489
}has_more_after true significa que falta o fim
Continue paginando enquanto has_more_after for true, usando o newest_id como próximo after. Para confirmar um envio recente, prefira a leitura sem cursor (as 20 mais recentes) em vez de after=0 — com 108 mensagens, after=0 devolve 100 e não inclui a última.
Interpretar cada mensagem
Três campos decidem o significado de uma mensagem. Ignorar qualquer um leva o agente a responder ao próprio texto ou a repetir para o cliente uma nota escrita entre atendentes.
| Campo | Valores | Leitura |
|---|---|---|
| message_type | 0 | 1 | 2 | 3 | 0 incoming (o cliente escreveu), 1 outgoing (a empresa escreveu — agente, bot ou automação), 2 activity (evento do sistema: status, atribuição), 3 template. O campo é numérico, não textual. |
| private | true | false | true é nota interna: o cliente nunca viu esse texto. Nunca cite o conteúdo de uma nota interna numa resposta ao cliente. |
| sender | type: contact | user | agent_bot | Quem escreveu. Compare sender.id com o id do próprio bot para reconhecer as próprias mensagens. Pode vir ausente em mensagens sem remetente (ex.: eventos de sistema). |
Notas internas exigem permissão própria
Sem conversation_private_notes_view, as mensagens com private: true são removidas da consulta, antes do limite de página. O bot recebe o histórico completo sem elas e nenhum aviso de que algo foi omitido. Com a permissão, elas chegam marcadas com private: true. Note que uma página pode vir com menos itens do que o teto sem que isso signifique fim do histórico.
Anexos: imagem, áudio e vídeo
Quando a mensagem tem mídia, ela vem em attachments. O campo data_url é uma URL assinada: baixa sem o header api_access_token. É o caminho para o agente enviar a imagem a um modelo de visão, o áudio a um modelo de transcrição ou o vídeo a um extrator de quadros.
{
"id": 88124,
"message_type": 0,
"content": "",
"content_type": "text",
"private": false,
"sender": { "id": 91, "name": "Ana Souza", "type": "contact" },
"attachments": [
{
"id": 5512,
"message_id": 88124,
"file_type": "image",
"account_id": 1,
"extension": "jpg",
"content_type": "image/jpeg",
"data_url": "https://chat.seudominio.com/rails/active_storage/blobs/redirect/...",
"thumb_url": "https://chat.seudominio.com/rails/active_storage/representations/...",
"file_size": 184322,
"width": 1280,
"height": 960
}
]
}Campos de attachments[]
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
file_type | string | Sim | image, áudio, vídeo, file, location, fallback, share, story_mention, contact, ig_reel, ig_post, ig_story, embed. |
data_url | string | Nao | URL assinada do binário. Baixa sem token. Para áudio, aponta para reprodução inline. |
thumb_url | string | Nao | Miniatura. Só existe para imagem; vazio nos demais tipos. |
transcribed_text | string | Nao | Presente em anexos de áudio. String vazia quando não há transcrição gravada. |
coordinates_lat / coordinates_long | number | Nao | Presentes quando file_type é location. |
Baixe no momento da leitura
Trate data_url como efêmera: ela redireciona para um destino de armazenamento com validade curta. Baixe o binário enquanto processa a mensagem em vez de guardar a URL para usar depois. Um anexo cuja mensagem foi apagada deixa de existir — a URL passa a falhar.
Responder na conversa
/api/v1/accounts/{account_id}/conversations/{display_id}/messagesCria uma mensagem. Sem message_type, a mensagem sai como outgoing (visível ao cliente).
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
content | string | Sim | Texto da mensagem. |
message_type | string | Nao | outgoing (padrão) ou incoming. |
private | boolean | Nao | true grava uma nota interna: o cliente não recebe nada. Padrão false. |
content_type | string | Nao | text (padrão) e demais tipos suportados pelo canal. |
template_params | object | Nao | Template aprovado pela Meta, para inbox WhatsApp Cloud fora da janela de 24h. |
echo_id | string | Nao | Identificador do cliente, devolvido no payload para correlação. |
curl -s -X POST "https://chat.seudominio.com/api/v1/accounts/1/conversations/318/messages" \
-H "api_access_token: SEU_TOKEN_DE_BOT" \
-H "Content-Type: application/json" \
-d '{
"content": "Consigo encaixar voce amanha as 14h. Confirma?",
"message_type": "outgoing",
"private": false
}'{
"id": 88130,
"content": "Consigo encaixar você amanha as 14h. Confirma?",
"message_type": 1,
"content_type": "text",
"private": false,
"conversation_id": 318,
"inbox_id": 42,
"created_at": 1771596300,
"sender": { "id": 7, "name": "Agente Hermes", "type": "agent_bot" }
}{ "error": "<mensagem>" }Nota interna é o caminho para raciocínio visível à equipe
Enviar com private: true grava uma nota que só a equipe lê. É o lugar correto para o agente registrar diagnóstico, dúvida ou justificativa de uma ação sem expor isso ao cliente. Uma nota interna nunca é entregue pelo canal.
Transferir para um humano
/api/v1/accounts/{account_id}/conversations/{display_id}/assignmentsAtribui a conversa a um agente humano ou a uma equipe. Exige conversation_manage.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
assignee_id | integer | Nao | Id do usuário que recebe a conversa. |
team_id | integer | Nao | Id da equipe, quando a atribuição for por time. |
curl -s -X POST "https://chat.seudominio.com/api/v1/accounts/1/conversations/318/assignments" \
-H "api_access_token: SEU_TOKEN_DE_BOT" \
-H "Content-Type: application/json" \
-d '{"assignee_id": 4}'O bot não lista agentes nem equipes
As rotas /agents e /teams estão fora da superfície de bot e respondem 401. Um agente que precisa transferir tem que receber os ids de destino na configuração — não há como descobri-los com o token do bot.
CRM / Pipeline
| Método | Rota | Permissão exigida |
|---|---|---|
| GET | /api/v1/accounts/{account_id}/pipelines | pipeline_manage | pipeline_view |
| GET | /api/v1/accounts/{account_id}/pipelines/{id} | pipeline_manage | pipeline_view |
| GET | /api/v1/accounts/{account_id}/pipelines/{id}/stages | pipeline_manage | pipeline_view |
| POST | /api/v1/accounts/{account_id}/pipelines | pipeline_manage |
| PATCH | /api/v1/accounts/{account_id}/pipelines/{id} | pipeline_manage |
| GET | /api/v1/accounts/{account_id}/pipeline_cards | pipeline_manage | pipeline_view |
| GET | /api/v1/accounts/{account_id}/pipeline_cards/{id} | pipeline_manage | pipeline_view |
| POST | /api/v1/accounts/{account_id}/pipeline_cards | pipeline_manage |
| PATCH | /api/v1/accounts/{account_id}/pipeline_cards/{id} | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline_cards/{id}/move_to_stage | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline_cards/{id}/recalculate_score | pipeline_manage |
| PATCH | /api/v1/accounts/{account_id}/pipeline_cards/{id}/update_qualification_checklist | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline_cards/reorder | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/deal_status/mark_won | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/deal_status/mark_lost | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/deal_status/reopen | pipeline_manage |
| GET | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/opportunities | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/opportunities | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline/opportunities/{id}/void | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/conversations | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/contacts | pipeline_manage |
| PATCH | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/assign | pipeline_manage |
| GET | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachments | pipeline_manage |
| POST | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachments | pipeline_manage |
| GET | /api/v1/accounts/{account_id}/pipeline/cards/{card_id}/timeline | pipeline_manage | pipeline_view |
| GET | /api/v1/accounts/{account_id}/pipeline/activities | pipeline_manage | pipeline_view |
| POST | /api/v1/accounts/{account_id}/pipeline/activities | pipeline_manage |
| GET | /api/v1/accounts/{account_id}/pipeline/products | pipeline_manage | pipeline_view |
| GET | /api/v1/accounts/{account_id}/pipeline/products/performance | pipeline_manage | pipeline_view |
| GET | /api/v1/accounts/{account_id}/pipeline/products/{id} | pipeline_manage | pipeline_view |
Cards exigem uma quarta trava: opt-in por funil
Além da permissão, toda rota que resolve um card existente passa pelo recorte de funis que listaram este bot na configuração do funil — listar, abrir, atualizar, mover, fechar como ganho ou perdido, reabrir, anexar, vincular contato ou conversa, atribuir responsável, ler a timeline, registrar oportunidade e criar atividade. Um bot com pipeline_manage num funil que não o listou recebe lista vazia em GET /pipeline_cards e 404 ao pedir um card pelo id. Esse opt-in é feito pelo administrador, funil a funil, e não vem ligado por padrão para bot nenhum. Atualizar a versão do NooviChat não liga.
POST /pipeline_cards é a exceção: criar um card não exige o opt-in. O card é criado com 200 e, se o funil não listou o bot, some da vista dele em seguida — nenhuma leitura posterior o encontra.
Listar funis não prova acesso a cards
GET /pipelines usa um recorte diferente do de cards: um funil pode aparecer nessa lista e mesmo assim não devolver card algum, porque não fez o opt-in do bot. Não deduza acesso a cards a partir da listagem de funis — teste com GET /pipeline_cards?pipeline_id=.
Achar o card da conversa
/api/v1/accounts/{account_id}/pipeline_cards?conversation_display_id={display_id}Lista os cards vinculados a uma conversa. É o caminho para o agente ligar o que está lendo ao negócio em andamento.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
conversation_display_id(query) | integer | Nao | display_id da conversa. O mesmo id usado nas rotas de conversa. |
include_linked_conversations(query) | boolean | Nao | true também traz cards em que essa conversa é um vínculo secundário, não o principal. |
pipeline_id(query) | integer | Nao | Restringe a um funil. |
contact_id(query) | integer | Nao | Restringe a um contato. |
limit(query) | integer | Nao | Padrão 50, máximo 500. Valores acima são reduzidos ao teto.(default: 50) |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards?conversation_display_id=318&include_linked_conversations=true" \
-H "api_access_token: SEU_TOKEN_DE_BOT"{
"payload": [
{
"id": 4412,
"title": "Ana Souza - Consulta",
"pipeline_id": 3,
"pipeline_stage": "negociação",
"conversation_display_id": 318,
"contact_id": 91,
"value": "980.0",
"priority": "high",
"won_at": null,
"lost_at": null
}
]
}Mover o card de etapa
/api/v1/accounts/{account_id}/pipeline_cards/{id}/move_to_stageMove o card. Etapas de ganho e de perda têm tratamento próprio.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline_stage | string | Sim | Chave da etapa de destino, conforme GET /pipelines/{id}/stages. |
won_value | string | Nao | Valor fechado, quando a etapa de destino for de ganho. |
won_note | string | Nao | Observação do fechamento. |
curl -s -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards/4412/move_to_stage" \
-H "api_access_token: SEU_TOKEN_DE_BOT" \
-H "Content-Type: application/json" \
-d '{"pipeline_stage": "negociacao"}'{ "error": "Agent bots cannot mark pipeline cards as lost" }{
"error": "Invalid stage 'negociando'",
"available_stages": ["lead", "qualificado", "negociação", "Ganho", "Perdido"]
}{ "error": "<mensagem>" }Ganho e perda têm portas diferentes para bot
Mover para uma etapa de ganho funciona por move_to_stage e por POST /pipeline/cards/{card_id}/deal_status/mark_won, ambas exigindo o opt-in do funil. Mover para uma etapa de perda por move_to_stage é recusado com 403; a rota deal_status/mark_lost continua na superfície e exige pipeline_manage. Se a sua política é que a IA nunca encerre um negócio como perdido, não conceda pipeline_manage ao bot.
Registrar o que aconteceu
/api/v1/accounts/{account_id}/pipeline/activitiesCria uma atividade no card. O id do card vai no corpo, não no path.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline_card_id | integer | Sim | Card que recebe a atividade. Deve estar em funil com opt-in do bot. |
activity_type | string | Nao | Tipo da atividade conforme o catálogo do funil. |
title | string | Nao | Título. |
description | string | Nao | Descrição livre. |
{ "errors": ["<mensagem>"] }Catálogo de produtos: leitura funciona, escrita não
GET /pipeline/products e GET /pipeline/products/performance respondem normalmente para um bot com pipeline_view ou pipeline_manage. Já POST e PATCH em /pipeline/products não funcionam para bot: a autorização de escrita do catálogo exige uma função personalizada persistida, que um bot não tem, e a resposta é 403 mesmo com pipeline_manage. Trate o catálogo como somente leitura para agentes de IA.
Agendamento
Toda a superfície de Agendamento exige appointment_manage — uma única permissão cobre ler agenda, consultar disponibilidade e criar. Nesta área a negação de policy responde 403 com {"error":"forbidden"}, e não 401.
| Método | Rota | Permissão exigida |
|---|---|---|
| GET | /api/v1/accounts/{account_id}/appointments | appointment_manage |
| GET | /api/v1/accounts/{account_id}/appointments/{id} | appointment_manage |
| POST | /api/v1/accounts/{account_id}/appointments | appointment_manage |
| PATCH | /api/v1/accounts/{account_id}/appointments/{id} | appointment_manage |
| POST | /api/v1/accounts/{account_id}/appointments/{id}/confirm | appointment_manage |
| POST | /api/v1/accounts/{account_id}/appointments/{id}/complete | appointment_manage |
| POST | /api/v1/accounts/{account_id}/appointments/{id}/no_show | appointment_manage |
| GET | /api/v1/accounts/{account_id}/appointments/availability | appointment_manage |
| GET | /api/v1/accounts/{account_id}/appointments/availability_range | appointment_manage |
| GET | /api/v1/accounts/{account_id}/appointments/available_professionals | appointment_manage |
| GET | /api/v1/accounts/{account_id}/appointments/clients | appointment_manage |
| GET | /api/v1/accounts/{account_id}/professionals | appointment_manage |
| GET | /api/v1/accounts/{account_id}/professionals/{id} | appointment_manage |
| GET | /api/v1/accounts/{account_id}/professionals/{id}/availability | appointment_manage |
| GET | /api/v1/accounts/{account_id}/services | appointment_manage |
| GET | /api/v1/accounts/{account_id}/services/{id} | appointment_manage |
Consultar horários livres
/api/v1/accounts/{account_id}/appointments/availabilityHorários livres de um profissional em uma data.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
professional_id(query) | integer | Sim | Profissional consultado. |
date(query) | string | Sim | Data no formato YYYY-MM-DD. |
service_id(query) | integer | Nao | Quando informado, a duração do slot vem do serviço e ignora duration_minutes. |
duration_minutes(query) | integer | Nao | Duração desejada, usada quando não há service_id. |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/appointments/availability?professional_id=12&service_id=5&date=2026-08-25" \
-H "api_access_token: SEU_TOKEN_DE_BOT"{
"data": {
"date": "2026-08-25",
"professional_id": 12,
"slots": ["2026-08-25T09:00:00-03:00", "2026-08-25T14:00:00-03:00"]
}
}{ "error": "<mensagem>" }Grade de vários dias
GET /appointments/availability_range responde por um intervalo em vez de um dia, até o teto de 42 dias. Use essa rota em vez de repetir a consulta diária.
Criar o agendamento
/api/v1/accounts/{account_id}/appointmentsCria um agendamento. Campos fora da lista abaixo fazem a requisição falhar com 422 — não são ignorados.
Body (dentro da chave appointment)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
contact_id | integer | Sim | Contato atendido. |
professional_id | integer | Sim | Profissional. |
service_id | integer | Sim | Serviço prestado. |
scheduled_at | string | Sim | Início, em ISO 8601 com fuso. |
ends_at | string | Nao | Fim. Se omitido, deriva da duração do serviço. |
notes | string | Nao | Observações. |
partner_id | integer | Nao | Parceiro vinculado. |
conversation_display_id | integer | Nao | Liga o agendamento à conversa que o originou. |
pipeline_card_id | integer | Nao | Liga o agendamento ao card do funil. |
custom_attributes | object | Nao | Atributos livres. |
curl -s -X POST "https://chat.seudominio.com/api/v1/accounts/1/appointments" \
-H "api_access_token: SEU_TOKEN_DE_BOT" \
-H "Content-Type: application/json" \
-d '{
"appointment": {
"contact_id": 91,
"professional_id": 12,
"service_id": 5,
"scheduled_at": "2026-08-25T14:00:00-03:00",
"conversation_display_id": 318,
"notes": "Agendado pelo agente de IA a partir da conversa."
}
}'Atualizar só aceita quatro campos
PATCH /appointments/{id} aceita apenas scheduled_at, notes, partner_id e custom_attributes. Enviar ends_at, trocar de profissional ou de serviço pelo update responde 422. Remarcar é mudar scheduled_at; trocar de serviço exige cancelar e criar de novo — e cancelar está fora da superfície de bot.
Ciclo de vida disponível ao bot
confirm, complete e no_show estão na superfície de bot. cancel e apagar não estão: respondem 401. Um agente que precise desmarcar deve escalar para um humano.
Follow-up
Há dois caminhos, com contratos diferentes. O follow-up individual é uma mensagem programada com texto livre naquela conversa. A regra de etapa é uma cadência do funil: quando um card entra em determinada etapa, o follow-up nasce sozinho.
Follow-up individual
/api/v1/accounts/{account_id}/conversations/{display_id}/follow-upsAgenda uma mensagem para o futuro nesta conversa. Exige follow_up_manage.
Body (dentro da chave follow_up)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
content | string | Sim | Texto que será enviado. |
scheduled_at | string | Sim | Quando enviar, em ISO 8601 com fuso. Precisa estar no futuro (tolerância de 1 minuto). |
title | string | Nao | Título interno. |
inbox_id | integer | Nao | Inbox de envio. Quando informada, precisa ser a mesma inbox da conversa. |
follow_up_template_id | integer | Nao | Template de follow-up da conta. |
template_params | object | Nao | Template aprovado pela Meta, para inbox WhatsApp Cloud. |
curl -s -X POST "https://chat.seudominio.com/api/v1/accounts/1/conversations/318/follow-ups" \
-H "api_access_token: SEU_TOKEN_DE_BOT" \
-H "Content-Type: application/json" \
-d '{
"follow_up": {
"content": "Oi Ana! Passando para confirmar seu horario de amanha as 14h.",
"scheduled_at": "2026-08-24T18:00:00-03:00",
"title": "Confirmacao D-1"
}
}'{
"id": 771,
"message": "Oi Ana! Passando para confirmar seu horário de amanha as 14h.",
"scheduled_at": 1771966800,
"title": "Confirmação D-1",
"inbox_id": 42,
"conversation_id": 318,
"created_at": 1771596400,
"status": "pending"
}{ "error": "Scheduled at must be in the future" }O bot só enxerga os próprios follow-ups
GET .../follow-ups e GET .../follow-ups/count devolvem apenas os follow-ups criados por este bot. Follow-ups agendados por pessoas da equipe ou por outro bot na mesma conversa não aparecem, e count reflete o mesmo recorte. Um agente que verifica já existe follow-up agendado? por essa rota está perguntando eu já agendei? — não alguém já agendou?.
Regra de follow-up por etapa
/api/v1/accounts/{account_id}/pipelines/{pipeline_id}/follow-up-rulesCria uma cadência disparada por mudança de etapa. Exige follow_up_manage OU pipeline_manage.
Body (dentro da chave pipeline_follow_up_rule)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
to_stage | string | Sim | Etapa de destino que dispara a regra. |
from_stage | string | Nao | Etapa de origem. Ausente ou null significa qualquer origem. |
content_mode | string | Nao | template (padrão) ou ai. Outro valor é recusado.(default: template) |
follow_up_template_id | integer | Nao | Obrigatório na criação quando content_mode é template. |
ai_instruction | string | Nao | Obrigatório quando content_mode é ai. É a instrução de redação. |
delay_minutes | integer | Nao | Espera antes do envio. 0 envia assim que possível.(default: 0) |
send_window | object | Nao | Janela de envio: { enabled, start, end, days }. Adia o disparo para dentro da janela. |
sender_id | integer | Nao | Usuário remetente. Exclusivo com sender_agent_bot_id. |
sender_agent_bot_id | integer | Nao | Bot remetente. O bot precisa servir a conta. Exclusivo com sender_id. |
enabled | boolean | Nao | Liga ou desliga a regra.(default: true) |
curl -s -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipelines/3/follow-up-rules" \
-H "api_access_token: SEU_TOKEN_DE_BOT" \
-H "Content-Type: application/json" \
-d '{
"pipeline_follow_up_rule": {
"to_stage": "negociacao",
"from_stage": null,
"content_mode": "ai",
"ai_instruction": "Retome a conversa citando o servico discutido e ofereca dois horarios.",
"delay_minutes": 1440,
"sender_agent_bot_id": 7,
"send_window": { "enabled": true, "start": "08:00", "end": "18:00", "days": [1,2,3,4,5] }
}
}'{
"id": 55,
"pipeline_id": 3,
"follow_up_template_id": null,
"sender_id": null,
"sender_agent_bot_id": 7,
"sender_agent_bot_name": "Agente Hermes",
"content_mode": "ai",
"ai_instruction": "Retome a conversa citando o serviço discutido e ofereca dois horários.",
"from_stage": null,
"to_stage": "negociação",
"delay_minutes": 1440,
"send_window": { "enabled": true, "start": "08:00", "end": "18:00", "days": [1,2,3,4,5] },
"enabled": true,
"conditions": {},
"created_at": 1771596500,
"updated_at": 1771596500
}{ "errors": ["Ai instruction nao pode ficar em branco"] }{ "error": "<mensagem no idioma da conta>" }Os dois modos de conteúdo
template renderiza um template de follow-up da conta no momento em que a regra dispara. ai não carrega template: a redação acontece na hora do envio, a partir de ai_instruction e do estado da conversa naquele momento — é por isso que o texto não fica congelado no instante em que o card mudou de etapa. Não existe um terceiro modo.
Regra só dispara com conversa vinculada
A regra é ignorada quando o card não tem conversa vinculada, quando a conversa apontada não existe naquela conta, ou quando ela não tem inbox. Nesses casos não há erro em resposta alguma: a regra foi criada com 201, o card muda de etapa com 200, e o follow-up simplesmente não nasce — o motivo fica apenas no log do servidor. Antes de confiar numa regra, confirme que os cards do funil têm conversation_display_id preenchido. Para vincular, use POST /pipeline/cards/{card_id}/conversations ou grave conversation_display_id no card.
Autoria de bot no follow-up gerado
Com sender_agent_bot_id apontando para o próprio bot, os follow-ups nascidos daquela regra ficam com autoria de bot — e passam a aparecer nas listagens que o bot faz. Sem esse campo, a autoria cai na cadeia responsável do card, depois responsável da conversa, depois administrador, e o bot não verá esses follow-ups.
Não suportado
Itens abaixo não existem hoje. Não há parâmetro, header ou permissão que os habilite.
Authorization: Bearernesta API. O único header aceito éapi_access_token.- Token de bot válido para mais de uma conta.
- Qualquer ação de apagar (
destroy) em qualquer recurso. - Cancelar ou reenviar follow-up, cancelar agendamento, apagar card ou conversa.
- Criar ou alterar produto do catálogo do Pipeline (responde
403mesmo compipeline_manage). - Mover card para etapa de perda por
move_to_stage(responde403). - Listar agentes, equipes, inboxes, webhooks, automações, relatórios ou central de ajuda com token de bot.
- Autoprovisionamento: um bot não cria bots, não altera as próprias permissões e não vincula a si mesmo a uma inbox.
- Parâmetro de tamanho de página no histórico de mensagens.
- Distinguir, pela resposta, um
404de conta errada de um404de recurso fora do recorte de visibilidade.