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.

TravaPerguntaFalha responde
1. SuperfícieA rota e a ação estão na lista de superfície de bot?401
2. PermissãoO bot carrega ao menos uma das permissões exigidas por aquela superfície?401
3. Vínculo de contaO 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ãoAuthorization: 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.

bash
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

NomeTipoObrigatorioDescricao
api_access_token(header)stringSimToken do Agent Bot. Identifica o bot e a conta que ele serve.
Content-Type(header)stringNaoapplication/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.

GET/api/v1/accounts/{account_id}/agent_bots/me

Devolve as permissões concedidas, as caixas de entrada vinculadas e a lista de superfícies com o campo granted.

json
{
  "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ãoO que libera
conversation_manageTodas as conversas da conta
conversation_unassigned_manageConversas não atribuídas e as próprias
conversation_participating_manageConversas próprias ou em que participa
conversation_awaiting_reply_viewVer a lista "Sem resposta"
contact_manageCRUD de contatos
report_manageRelatórios
knowledge_base_managePortais e artigos da central de ajuda
pipeline_managePipeline: CRUD e automações
pipeline_viewPipeline: somente leitura
follow_up_manageFollow-up: templates e automações
appointment_manageAtendimentos: ações privilegiadas e configuração
commercial_analysis_manageAnálise Comercial
conversation_private_notes_viewLer 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.

POST/api/v1/accounts/{account_id}/agent_bots

1. Cria o bot. O token de acesso é devolvido apenas para administradores.

bash
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"
  }'
200Bot criado. Guarde access_token — é o valor do header api_access_token.
json
{
  "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
}
PATCH/api/v1/accounts/{account_id}/agent_bots/{id}

2. Concede as permissões. bot_config é substituído inteiro — envie sempre a lista completa.

bash
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"
      ]
    }
  }'
422Permissão fora do catálogo.
json
{
  "message": "unknown bot permissions: pipeline_admin",
  "attributes": ["base"]
}
POST/api/v1/accounts/{account_id}/inboxes/{inbox_id}/set_agent_bot

3. Vincula o bot a uma inbox. É este vínculo que faz o bot servir a conta.

Body

NomeTipoObrigatorioDescricao
agent_botintegerSimId do bot. Enviar null remove o vínculo da inbox.
bash
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}'
200Vínculo criado. O corpo é vazio.

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íciePermissão que abreAções liberadas
Conversasconversation_manage OU conversation_unassigned_manage OU conversation_participating_manageindex, show, filter, search, create, update, meta, toggle_status, toggle_priority, toggle_typing_status, custom_attributes, unread, update_last_seen, mute, unmute, attachments
Mensagens da conversaconversation_manage OU conversation_unassigned_manage OU conversation_participating_manageindex, create
Atribuição de conversaconversation_managecreate
Etiquetas da conversaconversation_manageindex, create
Follow-ups da conversafollow_up_manageindex, show, create, update, count
Contatoscontact_manageindex, show, search, create, update
Funis (pipelines)pipeline_manage OU pipeline_viewindex, show, stages, create, update
Cards do funilpipeline_manage OU pipeline_viewindex, show, create, update, move_to_stage, reorder, recalculate_score, update_qualification_checklist
Status do negóciopipeline_managemark_won, mark_lost, reopen
Catálogo de produtospipeline_manage OU pipeline_viewindex, show, create, update, performance
Oportunidades (razão financeiro)pipeline_manageindex, create, void
Vínculo card ↔ conversapipeline_managecreate
Vínculo card ↔ contatopipeline_managecreate
Atividades do funilpipeline_manage OU pipeline_viewindex, create, timeline
Anexos do cardpipeline_manageindex, create
Responsável do cardpipeline_manageassign
Regras de follow-up do funilfollow_up_manage OU pipeline_manageindex, show, create, update
Agendamentosappointment_manageindex, show, create, update, confirm, complete, no_show, availability, availability_range, available_professionals, clients
Profissionaisappointment_manageindex, show, availability
Serviçosappointment_manageindex, 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.

StatusCorpoQuando
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étodoRotaPermissão exigida
GET/api/v1/accounts/{account_id}/conversationsconversation_manage | conversation_unassigned_manage | conversation_participating_manage
GET/api/v1/accounts/{account_id}/conversations/metaidem
GET/api/v1/accounts/{account_id}/conversations/search?q=idem
POST/api/v1/accounts/{account_id}/conversations/filteridem
GET/api/v1/accounts/{account_id}/conversations/{display_id}idem
POST/api/v1/accounts/{account_id}/conversationsidem
PATCH/api/v1/accounts/{account_id}/conversations/{display_id}idem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/toggle_statusidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/toggle_priorityidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/toggle_typing_statusidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/custom_attributesidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/unreadidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/update_last_seenidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/muteidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/unmuteidem
GET/api/v1/accounts/{account_id}/conversations/{display_id}/attachmentsidem
GET/api/v1/accounts/{account_id}/conversations/{display_id}/messagesidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/messagesidem
POST/api/v1/accounts/{account_id}/conversations/{display_id}/assignmentsconversation_manage
GET/api/v1/accounts/{account_id}/conversations/{display_id}/labelsconversation_manage
POST/api/v1/accounts/{account_id}/conversations/{display_id}/labelsconversation_manage
GET/api/v1/accounts/{account_id}/contactscontact_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}/contactscontact_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

GET/api/v1/accounts/{account_id}/conversations/{display_id}/messages

Retorna mensagens da conversa. O identificador no path é o display_id da conversa, não o id interno.

Query

NomeTipoObrigatorioDescricao
after(query)integerNaoCursor. Devolve até 100 mensagens com id maior que o valor, em ordem cronológica crescente.
before(query)integerNaoCursor. Devolve até 20 mensagens com id menor que o valor, em ordem cronológica crescente.
bash
curl -s "https://chat.seudominio.com/api/v1/accounts/1/conversations/318/messages" \
  -H "api_access_token: SEU_TOKEN_DE_BOT"
200As mensagens vêm em payload; meta traz contato, responsável e etiquetas.
json
{
  "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.

bash
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
done

Limites 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.

json
"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.

CampoValoresLeitura
message_type0 | 1 | 2 | 30 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.
privatetrue | falsetrue é nota interna: o cliente nunca viu esse texto. Nunca cite o conteúdo de uma nota interna numa resposta ao cliente.
sendertype: contact | user | agent_botQuem 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.

json
{
  "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[]

NomeTipoObrigatorioDescricao
file_typestringSimimage, áudio, vídeo, file, location, fallback, share, story_mention, contact, ig_reel, ig_post, ig_story, embed.
data_urlstringNaoURL assinada do binário. Baixa sem token. Para áudio, aponta para reprodução inline.
thumb_urlstringNaoMiniatura. Só existe para imagem; vazio nos demais tipos.
transcribed_textstringNaoPresente em anexos de áudio. String vazia quando não há transcrição gravada.
coordinates_lat / coordinates_longnumberNaoPresentes 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

POST/api/v1/accounts/{account_id}/conversations/{display_id}/messages

Cria uma mensagem. Sem message_type, a mensagem sai como outgoing (visível ao cliente).

Body

NomeTipoObrigatorioDescricao
contentstringSimTexto da mensagem.
message_typestringNaooutgoing (padrão) ou incoming.
privatebooleanNaotrue grava uma nota interna: o cliente não recebe nada. Padrão false.
content_typestringNaotext (padrão) e demais tipos suportados pelo canal.
template_paramsobjectNaoTemplate aprovado pela Meta, para inbox WhatsApp Cloud fora da janela de 24h.
echo_idstringNaoIdentificador do cliente, devolvido no payload para correlação.
bash
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
  }'
200A mensagem criada. sender.type é agent_bot — a autoria fica registrada no bot, não numa pessoa.
json
{
  "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" }
}
422Falha de validação ou de despacho no canal.
json
{ "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

POST/api/v1/accounts/{account_id}/conversations/{display_id}/assignments

Atribui a conversa a um agente humano ou a uma equipe. Exige conversation_manage.

Body

NomeTipoObrigatorioDescricao
assignee_idintegerNaoId do usuário que recebe a conversa.
team_idintegerNaoId da equipe, quando a atribuição for por time.
bash
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étodoRotaPermissão exigida
GET/api/v1/accounts/{account_id}/pipelinespipeline_manage | pipeline_view
GET/api/v1/accounts/{account_id}/pipelines/{id}pipeline_manage | pipeline_view
GET/api/v1/accounts/{account_id}/pipelines/{id}/stagespipeline_manage | pipeline_view
POST/api/v1/accounts/{account_id}/pipelinespipeline_manage
PATCH/api/v1/accounts/{account_id}/pipelines/{id}pipeline_manage
GET/api/v1/accounts/{account_id}/pipeline_cardspipeline_manage | pipeline_view
GET/api/v1/accounts/{account_id}/pipeline_cards/{id}pipeline_manage | pipeline_view
POST/api/v1/accounts/{account_id}/pipeline_cardspipeline_manage
PATCH/api/v1/accounts/{account_id}/pipeline_cards/{id}pipeline_manage
POST/api/v1/accounts/{account_id}/pipeline_cards/{id}/move_to_stagepipeline_manage
POST/api/v1/accounts/{account_id}/pipeline_cards/{id}/recalculate_scorepipeline_manage
PATCH/api/v1/accounts/{account_id}/pipeline_cards/{id}/update_qualification_checklistpipeline_manage
POST/api/v1/accounts/{account_id}/pipeline_cards/reorderpipeline_manage
POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/deal_status/mark_wonpipeline_manage
POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/deal_status/mark_lostpipeline_manage
POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/deal_status/reopenpipeline_manage
GET/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/opportunitiespipeline_manage
POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/opportunitiespipeline_manage
POST/api/v1/accounts/{account_id}/pipeline/opportunities/{id}/voidpipeline_manage
POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/conversationspipeline_manage
POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/contactspipeline_manage
PATCH/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/assignpipeline_manage
GET/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachmentspipeline_manage
POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachmentspipeline_manage
GET/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/timelinepipeline_manage | pipeline_view
GET/api/v1/accounts/{account_id}/pipeline/activitiespipeline_manage | pipeline_view
POST/api/v1/accounts/{account_id}/pipeline/activitiespipeline_manage
GET/api/v1/accounts/{account_id}/pipeline/productspipeline_manage | pipeline_view
GET/api/v1/accounts/{account_id}/pipeline/products/performancepipeline_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

GET/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

NomeTipoObrigatorioDescricao
conversation_display_id(query)integerNaodisplay_id da conversa. O mesmo id usado nas rotas de conversa.
include_linked_conversations(query)booleanNaotrue também traz cards em que essa conversa é um vínculo secundário, não o principal.
pipeline_id(query)integerNaoRestringe a um funil.
contact_id(query)integerNaoRestringe a um contato.
limit(query)integerNaoPadrão 50, máximo 500. Valores acima são reduzidos ao teto.(default: 50)
bash
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"
200Lista vazia significa: não há card, ou há card em funil que não fez opt-in deste bot.
json
{
  "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

POST/api/v1/accounts/{account_id}/pipeline_cards/{id}/move_to_stage

Move o card. Etapas de ganho e de perda têm tratamento próprio.

Body

NomeTipoObrigatorioDescricao
pipeline_stagestringSimChave da etapa de destino, conforme GET /pipelines/{id}/stages.
won_valuestringNaoValor fechado, quando a etapa de destino for de ganho.
won_notestringNaoObservação do fechamento.
bash
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"}'
200O card atualizado.
403Etapa de destino marcada como perdida.
json
{ "error": "Agent bots cannot mark pipeline cards as lost" }
422Etapa ausente ou inexistente no funil.
json
{
  "error": "Invalid stage 'negociando'",
  "available_stages": ["lead", "qualificado", "negociação", "Ganho", "Perdido"]
}
409O card já estava fechado como ganho.
json
{ "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

POST/api/v1/accounts/{account_id}/pipeline/activities

Cria uma atividade no card. O id do card vai no corpo, não no path.

Body

NomeTipoObrigatorioDescricao
pipeline_card_idintegerSimCard que recebe a atividade. Deve estar em funil com opt-in do bot.
activity_typestringNaoTipo da atividade conforme o catálogo do funil.
titlestringNaoTítulo.
descriptionstringNaoDescrição livre.
201Atividade criada.
422Validação recusada.
json
{ "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étodoRotaPermissão exigida
GET/api/v1/accounts/{account_id}/appointmentsappointment_manage
GET/api/v1/accounts/{account_id}/appointments/{id}appointment_manage
POST/api/v1/accounts/{account_id}/appointmentsappointment_manage
PATCH/api/v1/accounts/{account_id}/appointments/{id}appointment_manage
POST/api/v1/accounts/{account_id}/appointments/{id}/confirmappointment_manage
POST/api/v1/accounts/{account_id}/appointments/{id}/completeappointment_manage
POST/api/v1/accounts/{account_id}/appointments/{id}/no_showappointment_manage
GET/api/v1/accounts/{account_id}/appointments/availabilityappointment_manage
GET/api/v1/accounts/{account_id}/appointments/availability_rangeappointment_manage
GET/api/v1/accounts/{account_id}/appointments/available_professionalsappointment_manage
GET/api/v1/accounts/{account_id}/appointments/clientsappointment_manage
GET/api/v1/accounts/{account_id}/professionalsappointment_manage
GET/api/v1/accounts/{account_id}/professionals/{id}appointment_manage
GET/api/v1/accounts/{account_id}/professionals/{id}/availabilityappointment_manage
GET/api/v1/accounts/{account_id}/servicesappointment_manage
GET/api/v1/accounts/{account_id}/services/{id}appointment_manage

Consultar horários livres

GET/api/v1/accounts/{account_id}/appointments/availability

Horários livres de um profissional em uma data.

Query

NomeTipoObrigatorioDescricao
professional_id(query)integerSimProfissional consultado.
date(query)stringSimData no formato YYYY-MM-DD.
service_id(query)integerNaoQuando informado, a duração do slot vem do serviço e ignora duration_minutes.
duration_minutes(query)integerNaoDuração desejada, usada quando não há service_id.
bash
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"
200Slots em ISO 8601, no fuso configurado na conta.
json
{
  "data": {
    "date": "2026-08-25",
    "professional_id": 12,
    "slots": ["2026-08-25T09:00:00-03:00", "2026-08-25T14:00:00-03:00"]
  }
}
404Profissional inexistente na conta.
json
{ "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

POST/api/v1/accounts/{account_id}/appointments

Cria um agendamento. Campos fora da lista abaixo fazem a requisição falhar com 422 — não são ignorados.

Body (dentro da chave appointment)

NomeTipoObrigatorioDescricao
contact_idintegerSimContato atendido.
professional_idintegerSimProfissional.
service_idintegerSimServiço prestado.
scheduled_atstringSimInício, em ISO 8601 com fuso.
ends_atstringNaoFim. Se omitido, deriva da duração do serviço.
notesstringNaoObservações.
partner_idintegerNaoParceiro vinculado.
conversation_display_idintegerNaoLiga o agendamento à conversa que o originou.
pipeline_card_idintegerNaoLiga o agendamento ao card do funil.
custom_attributesobjectNaoAtributos livres.
bash
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."
    }
  }'
201Agendamento criado.

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

POST/api/v1/accounts/{account_id}/conversations/{display_id}/follow-ups

Agenda uma mensagem para o futuro nesta conversa. Exige follow_up_manage.

Body (dentro da chave follow_up)

NomeTipoObrigatorioDescricao
contentstringSimTexto que será enviado.
scheduled_atstringSimQuando enviar, em ISO 8601 com fuso. Precisa estar no futuro (tolerância de 1 minuto).
titlestringNaoTítulo interno.
inbox_idintegerNaoInbox de envio. Quando informada, precisa ser a mesma inbox da conversa.
follow_up_template_idintegerNaoTemplate de follow-up da conta.
template_paramsobjectNaoTemplate aprovado pela Meta, para inbox WhatsApp Cloud.
bash
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"
    }
  }'
201Criado. O texto volta na chave message, não em content, e as datas são epoch em segundos.
json
{
  "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"
}
422Data no passado, inbox divergente da conversa ou conteúdo ausente.
json
{ "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

POST/api/v1/accounts/{account_id}/pipelines/{pipeline_id}/follow-up-rules

Cria uma cadência disparada por mudança de etapa. Exige follow_up_manage OU pipeline_manage.

Body (dentro da chave pipeline_follow_up_rule)

NomeTipoObrigatorioDescricao
to_stagestringSimEtapa de destino que dispara a regra.
from_stagestringNaoEtapa de origem. Ausente ou null significa qualquer origem.
content_modestringNaotemplate (padrão) ou ai. Outro valor é recusado.(default: template)
follow_up_template_idintegerNaoObrigatório na criação quando content_mode é template.
ai_instructionstringNaoObrigatório quando content_mode é ai. É a instrução de redação.
delay_minutesintegerNaoEspera antes do envio. 0 envia assim que possível.(default: 0)
send_windowobjectNaoJanela de envio: { enabled, start, end, days }. Adia o disparo para dentro da janela.
sender_idintegerNaoUsuário remetente. Exclusivo com sender_agent_bot_id.
sender_agent_bot_idintegerNaoBot remetente. O bot precisa servir a conta. Exclusivo com sender_id.
enabledbooleanNaoLiga ou desliga a regra.(default: true)
bash
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] }
    }
  }'
201Regra criada.
json
{
  "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
}
422Chave no plural e valor em array — formato diferente do follow-up individual.
json
{ "errors": ["Ai instruction nao pode ficar em branco"] }
403O funil restringe visibilidade e não inclui este ator.
json
{ "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: Bearer nesta 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 403 mesmo com pipeline_manage).
  • Mover card para etapa de perda por move_to_stage (responde 403).
  • 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 404 de conta errada de um 404 de recurso fora do recorte de visibilidade.