Pipeline (CRM)
Gerencie pipelines de vendas completos com estágios customizáveis, cards (deals), automações de workflow e lead scoring integrado.
Base URL
Todos os endpoints usam o prefixo /api/v1/accounts/{account_id}
Isolamento multi-tenant e segurança
Toda a API de Pipeline é escopada por conta. Você nunca consegue ler, alterar ou apagar dados de outra conta — nem por engano, nem de propósito. Entenda como o isolamento funciona antes de automatizar operações destrutivas.
O account_id vem da URL, nunca do corpo da requisição
O {account_id} faz parte do caminho da URL e é validado contra o seu token: a API confirma que o usuário autenticado é membro daquela conta antes de qualquer operação (caso contrário, 401). Nenhum endpoint lê account_id do corpo (body) da requisição. Por isso não existe o cenário de "esquecer de setar o account_id" e atingir outra conta — o escopo é sempre derivado da rota autenticada.
IDs no caminho são resolvidos dentro da sua conta
Todos os identificadores em {id}, display_id e conversation_display_id são buscados dentro do escopo da conta da URL(Current.account.pipeline_cards.find(...)). Um ID que pertence a outra conta retorna 404 Not Found — nunca o registro alheio.
Atenção: display_id e conversation_display_id são sequências por conta(a conta A e a conta B podem ambas ter o card #42), não identificadores globais. Use sempre o ID retornado pelos endpoints da sua própria conta; não tente adivinhar/iterar IDs.
Permissões por operação
- Pipelines: agentes podem consultar os pipelines visíveis; criar, atualizar e remover pipelines exige administrador.
- Cards: agentes podem consultar, criar, atualizar, mover e reordenar apenas em pipelines dos quais são membros. Excluir, acessar/restaurar a lixeira, excluir permanentemente, importar CSV, usar
bulk_assignebulk_actions/deleteexige administrador. - Anexos: agentes e administradores podem listar, baixar e enviar em cards visíveis; remover um anexo exige administrador.
- Automações do Pipeline: leitura respeita a visibilidade do pipeline; criar, atualizar, duplicar, importar, criar a partir de template, usar
dry_rune consultar rate limit exige administrador ou agente com a permissãopipeline_manage; remover, executar, exportar e ver/rotacionar a credencial de webhook exige administrador. Webhooks do Pipeline são inteiramente administrativos.
Falhas de permissão podem aparecer como 401 nos controllers legados e como 403 no namespace /pipeline.
Delete não cascateia entre contas — e é soft por padrão
DELETE /pipeline_cards/{id}é um soft delete (marcadiscarded_at), reversível. O hard delete exige um passo separado e explícito (permanently_delete, restrito a administradores e só para cards já descartados).DELETE /pipelines/{id}faz soft delete apenas dos cards daquela conta — nunca dispara uma cascata atingindo cards de outras contas.- Operações em lote (
bulk) ereorderfiltram os IDs recebidos pela sua conta antes de agir: qualquer ID de outra conta é silenciosamente ignorado (no-op), nunca apagado nem movido. Oreorderainda exige umpipeline_idde escopo para reforçar o isolamento entre pipelines: use o campo raiz canônico; por compatibilidade, o backend aceita opipeline_idda primeira posição como fallback legado.
Pipelines
/api/v1/accounts/{account_id}/pipelinesLista os pipelines visíveis ao usuário autenticado; administradores veem todos os pipelines da conta.
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipelines" \
-H "api_access_token: YOUR_TOKEN" | jq .[
{
"id": 1,
"account_id": 1,
"name": "Vendas B2B",
"description": "Pipeline principal de vendas",
"active": true,
"stages": {
"1_prospeccao": { "id": "1_prospeccao", "name": "Prospeccao", "color": "#3b82f6", "position": 0 },
"1_proposta": { "id": "1_proposta", "name": "Proposta", "color": "#f59e0b", "position": 1 },
"1_ganho": { "id": "1_ganho", "name": "Ganho", "color": "#10b981", "position": 2, "is_won_stage": true },
"1_perdido": { "id": "1_perdido", "name": "Perdido", "color": "#ef4444", "position": 3, "is_lost_stage": true }
},
"settings": { "require_won_value": false, "require_loss_reason": false },
"inbox_id": null,
"default_for_appointments": false,
"created_at": "2025-06-01T00:00:00Z",
"updated_at": "2025-06-01T00:00:00Z"
}
]/api/v1/accounts/{account_id}/pipelines/{id}Retorna um pipeline pelo ID, sem envelope, no mesmo formato de cada item da listagem.
Path
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
id(path) | integer | Sim | ID do pipeline, resolvido dentro da conta da URL (ID de outra conta retorna 404) |
Agent Bot só lê o pipeline que alcança
Para um Agent Bot, o pipeline precisa estar acessível ao bot (a mesma regra que filtra a listagem); fora disso a leitura é recusada com 401 ({ "error": "You are not authorized to do this action" }, sem reason). Para os estágios em formato de lista, com is_won_stage/is_lost_stage, use GET /pipelines/{id}/stages.
/api/v1/accounts/{account_id}/pipelines/{pipeline_id}/pipeline_cardsLista os cards de um pipeline. Rota aninhada equivalente a GET /pipeline_cards?pipeline_id={pipeline_id}: mesmo controller, mesmos filtros, mesma paginação (limit/offset ou cursor) e mesmo envelope { data, meta } — inclusive meta.stage_counts.
Path
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline_id(path) | integer | Sim | ID do pipeline cujos cards serão listados |
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipelines/1/pipeline_cards?pipeline_stage=1_proposta&limit=50" \
-H "api_access_token: YOUR_TOKEN" | jq ./api/v1/accounts/{account_id}/pipelinesCria um novo pipeline com estágios.
stages é obrigatório; prefira o objeto por ID
O campo stages é obrigatório. Envie pelo menos um estágio e use, como formato canônico, um objeto (hash) por ID. Arrays ainda são aceitos por compatibilidade, mas itens sem id recebem uma chave numérica de fallback; não dependa desse caminho legado. Use sempre Content-Type: application/json.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline.name | string | Sim | Nome do pipeline |
pipeline.description | string | Nao | Descrição |
pipeline.active | boolean | Nao | Status de ativação |
pipeline.inbox_id | integer | Nao | Inbox associada ao pipeline |
pipeline.stage_sla_enabled | boolean | Nao | Ativa o SLA por estágio |
pipeline.default_for_appointments | boolean | Nao | Marca este funil como destino padrão para agendamentos criados sem funil explícito. Marcar um funil desmarca automaticamente o que segurava o posto na mesma gravação; apenas um funil por conta pode ser default |
pipeline.agents | array | Nao | Agentes membros do pipeline; o backend incorpora a lista em settings.agents |
pipeline.settings | object | Nao | Configurações allowlisted do pipeline (max 64 KB) |
pipeline.stages | object | Sim | Objeto com estágios. Chaves são IDs temporários (string). Após criação os IDs são normalizados para {pipeline_id}_{slug} |
pipeline.stages.{key}.name | string | Sim | Nome do estágio |
pipeline.stages.{key}.color | string | Nao | Cor em hex (ex: "#3B82F6") |
pipeline.stages.{key}.icon | string | Nao | Ícone do estágio |
pipeline.stages.{key}.position | integer | Nao | Ordem crescente de exibição; o menor valor aparece primeiro (a interface normalmente inicia em 0) |
pipeline.stages.{key}.description | string | Nao | Descrição do estágio |
pipeline.stages.{key}.is_entry_stage | boolean | Nao | Marca o estágio de entrada |
pipeline.stages.{key}.is_won_stage | boolean | Nao | Marca o estágio terminal de ganho |
pipeline.stages.{key}.is_lost_stage | boolean | Nao | Marca o estágio terminal de perda |
pipeline.stages.{key}.automation_actions | JSON | Nao | Ações de automação associadas ao estágio |
pipeline.stages.{key}.wip_limit | integer | Nao | Limite de cards em andamento |
pipeline.stages.{key}.sla_hours | number | Nao | Prazo de SLA do estágio, em horas |
pipeline.stages.{key}.message_templates | array | Nao | Templates de mensagem associados ao estágio |
Chaves aceitas em pipeline.settings
card_display_fields, automations, notifications, visibility, sort_order, default_filter, quick_values, label_stage_mappings, stage_label_mappings, auto_assignment, require_loss_reason, enable_won_lost_stages, require_won_value, lead_scoring, agents, auto_archive_on_resolved e auto_win_on_resolved. Outras chaves são descartadas.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipelines" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pipeline": {
"name": "Vendas B2B",
"stages": {
"stage_1": { "name": "Prospeccao", "color": "#6366f1", "position": 0 },
"stage_2": { "name": "Qualificação", "color": "#3B82F6", "position": 1 },
"stage_3": { "name": "Proposta", "color": "#f59e0b", "position": 2 },
"stage_4": { "name": "Fechamento", "color": "#10b981", "position": 3 }
}
}
}'IDs dos estágios após criação
Após criar o pipeline, os IDs dos estágios são normalizados pelo servidor para o formato {pipeline_id}_{slug_do_nome} (ex: 1_prospeccao). Use GET /pipelines/{id}/stages para obter os IDs reais antes de criar cards.
/api/v1/accounts/{account_id}/pipelines/{id}Atualiza nome, descrição, estágios ou settings de um pipeline.
Agent Bot: escrita respeita o mesmo escopo da leitura
Para um Agent Bot, editar um pipeline exige a permissão de escrita e que o pipeline esteja no escopo do bot — lista agent_bots do pipeline vazia ou contendo o bot. Fora do escopo a resposta é 401 e o pipeline não é alterado, a mesma resposta de GET /pipelines/{id} para esse pipeline. Um bot só com pipeline_view não edita pipeline.
ATENÇÃO: formato do campo stages
Sempre envie stages como objeto (hash) com IDs reais como chaves. Arrays são aceitos por compatibilidade e preservam id quando ele existe; sem id, o backend usa o índice numérico como fallback. Esse payload não é rejeitado de forma geral e pode fazer estágios existentes parecerem removidos, movendo cards órfãos. Portanto, não envie arrays sem IDs.
A rota PATCH /pipelines/{id}/stages/{stage_id} não existe e retorna 404. Para editar uma stage individual (descrição, cor, nome, posição), envie PATCH /pipelines/{id} com o hashstagescompleto — o backend faz diff inteligente.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline.name | string | Nao | Nome do pipeline |
pipeline.description | string | Nao | Descrição |
pipeline.active | boolean | Nao | Status de ativação |
pipeline.inbox_id | integer | Nao | Inbox associada ao pipeline |
pipeline.stage_sla_enabled | boolean | Nao | Ativa o SLA por estágio |
pipeline.default_for_appointments | boolean | Nao | Marca este funil como destino padrão para agendamentos criados sem funil explícito. Marcar um funil desmarca automaticamente o que segurava o posto na mesma gravação; apenas um funil por conta pode ser default |
pipeline.agents | array | Nao | Agentes membros; mesclados em settings.agents |
pipeline.stages | object | Nao | Hash com stages keyed por ID real (ex: "1_qualificado"). Cada estágio aceita os mesmos campos da tabela de criação. |
pipeline.settings | object | Nao | Configurações allowlisted do pipeline (JSONB). Chaves desconhecidas são descartadas; o objeto enviado substitui o settings persistido. |
settings e agents não fazem deep-merge
Quando pipeline.settings é enviado, o objeto allowlisted substitui o blob atual. Enviar apenas pipeline.agents também constrói um novo settings com essa lista. Envie todas as configurações que devem permanecer; um payload parcial pode remover membros ou outras preferências.
curl -X PATCH "https://chat.seudominio.com/api/v1/accounts/1/pipelines/36" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pipeline": {
"stages": {
"36_qualificado": {
"name": "Qualificado",
"position": 1,
"description": "Lead qualificado para próxima etapa",
"color": "#10b981"
},
"36_entrada_de_lead": { "name": "Entrada de Lead", "position": 0 }
}
}
}'Comportamento ao remover stages
Quando uma stage existente é omitida do payload, ela é considerada removida e os cards que estavam nela são automaticamente movidos para a primeira stage não-terminal por ordem de position. Cada movimento é registrado em pipeline_stage_histories.
Limites: máximo de 50 stages por pipeline; settings JSON até 64 KB.
/api/v1/accounts/{account_id}/pipelines/{id}Remove um pipeline. Os cards associados são soft-deleted (discard), não apagados.
Escopo de conta e cascata
O id é resolvido dentro da conta autenticada(Current.account.pipelines.find) — não é um ID global. Tentar deletar um pipeline de outra conta retorna 404.
Ao destruir o pipeline, os cards associados são soft-deleted(discard!, marcando discarded_at) — não são hard-deleted — e pipeline_stage_histories, follow_up_rules e pipeline_webhooks são removidos em cascata. O próprio registro do pipeline é removido permanentemente; planeje com cuidado antes de chamar.
Estágios do Pipeline
/api/v1/accounts/{account_id}/pipelines/{pipeline_id}/stagesLista os estágios de um pipeline. O id de cada estágio é a string no formato {pipeline_id}_{slug} usada no campo pipeline_stage dos cards.
{
"pipeline_id": 8143,
"pipeline_name": "Clinica Medica - Jornada do Paciente",
"stages": [
{
"id": "8143_entrada_de_lead",
"name": "Entrada de Lead",
"color": "#64748b",
"icon": null,
"position": 0,
"description": "Entrada automática de leads via inbox",
"is_entry_stage": true,
"is_won_stage": false,
"is_lost_stage": false
},
{
"id": "8143_ganho",
"name": "Ganho",
"color": "#10b981",
"position": 6,
"is_entry_stage": false,
"is_won_stage": true,
"is_lost_stage": false
}
]
}Renomear um estágio muda o id (pipeline_stage)
O id do estágio é derivado do nome ({pipeline_id}_{slug}). Ao renomear um estágio via PATCH /pipelines/{id}, o backend re-slugifica a chave e migra os cards daquele estágio na mesma transação — ou seja, o valor de pipeline_stage muda. Integrações que armazenam o id do estágio devem reconsultar GET /pipelines/{id}/stages após mudanças de configuração.
Cards (Deals)
Cards representam oportunidades de venda (deals) dentro de um pipeline. Cada card possui valor, contato associado, estágio atual e lead score.
/api/v1/accounts/{account_id}/pipeline_cardsLista cards do kanban com filtros. Existem duas rotas: /pipeline_cards (legacy, paginação por cursor ou offset) e /pipeline/cards (canônica, paginação page/per_page). Ambas aceitam o mesmo conjunto completo de filtros avançados, incluindo busca textual server-side, via o concern compartilhado PipelineCardFilterable.
Filtros avançados (ambas as rotas, via concern compartilhado)
A rota /api/v1/accounts/{id}/pipeline_cards (legacy, paginação por limit com cursor ou offset) aceita o conjunto completo de filtros: além de pipeline_id, pipeline_stage, conversation_display_id e contact_id, ela também filtra por search, labels[], priority[], value_min/value_max, agent_id, date_start/date_end, status, sla_exceeded e stages[]. Os mesmos filtros são aplicados pelo endpoint de exportação (GET /pipeline/cards/export).
A rota /api/v1/accounts/{id}/pipeline/cards (canônica, paginação por page/per_page) aceita os mesmos filtros avançados (mesmo concern), e adicionalmente owner_id e lead_score_category (hot/warm/cold).
O parâmetro search faz busca textual server-side por título/descrição do card, contato, responsável, inbox, identificadores e nome do estágio. O termo é limitado aos primeiros 200 caracteres, pode ser combinado com os demais filtros e sempre respeita a conta autenticada e a visibilidade do usuário.
Importante: o filtro contact_id foi implementado na v4.12.3.0. Antes dessa versão, ele era silenciosamente ignorado e retornava todos os cards da conta. Se você tem automações que dependem desse filtro, confirme que está rodando v4.12.3.0 ou superior.
Parâmetros (rota legacy /pipeline_cards — conjunto completo)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline_id(query) | integer | Nao | Filtrar por pipeline (todos os cards desse funnel) |
pipeline_stage(query) | string | Nao | Filtrar por um estágio específico (ex: "1_lead") |
conversation_display_id(query) | integer | Nao | Filtrar por conversa associada (display_id, não ID global) |
contact_id(query) | integer | Nao | Filtrar por contato (v4.12.3.0+) |
search(query) | string | Nao | Busca textual server-side (max 200 caracteres) por card, contato, responsável, inbox, identificadores ou estágio. |
labels[](query) | string[] | Nao | Títulos de labels da conversa vinculada ao card. Match OR (qualquer um dos títulos). |
priority[](query) | string[] | Nao | Prioridade do card: none, low, medium, high, urgent. Match OR. |
value_min(query) | number | Nao | Valor mínimo do card (>=). Valor do card = expected_revenue ou item_details.value. Cards sem valor algum são EXCLUÍDOS quando value_min/value_max está presente. |
value_max(query) | number | Nao | Valor máximo do card (<=). Cards sem valor algum são excluídos (filtrar por valor implica ter valor). |
agent_id(query) | integer | Nao | ID do responsável (owner) do card. Use -1 ou "unassigned" para cards sem responsável. |
date_start(query) | string | Nao | Data inicial de criação (created_at), formato YYYY-MM-DD, interpretada no fuso da conta. |
date_end(query) | string | Nao | Data final de criação (created_at), formato YYYY-MM-DD, fuso da conta. |
status(query) | string | Nao | Estado do deal: open, won, lost ou closed. |
sla_exceeded(query) | boolean | Nao | true = apenas cards abertos com SLA vencido (sla_overdue). |
stages[](query) | string[] | Nao | Lista de estágios (pipeline_stage). Match OR — cards em qualquer um dos estágios informados. |
limit(query) | integer | Nao | Itens por página (default 50). O valor é limitado ao intervalo de 1 a 500. |
cursor(query) | string | Nao | Cursor opaco retornado em meta.next_cursor. Use para buscar a próxima página sem recalcular offset. |
offset(query) | integer | Nao | Offset de compatibilidade, usado quando cursor não é enviado.(default: 0) |
Parâmetros extras da rota canônica /pipeline/cards (além dos filtros avançados acima)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline_id(query) | integer | Nao | Filtrar por pipeline |
pipeline_stage(query) | string | Nao | Filtrar por estágio (ex: "69_lead") |
owner_id(query) | integer | Nao | Filtrar por responsável (além de agent_id da tabela acima) |
contact_id(query) | integer | Nao | Filtrar por contato (v4.12.3.0+) |
lead_score_category(query) | string | Nao | hot, warm, cold (exclusivo da canônica) |
page(query) | integer | Nao | Página atual(default: 1) |
per_page(query) | integer | Nao | Itens por página (máximo 100)(default: 25) |
Como percorrer todos os cards com cursor
Na primeira chamada, omita cursor. Enquanto meta.has_more for true, repita a requisição com os mesmos filtros e envie cursor=meta.next_cursor. Pare quando has_more=false. Trate o cursor como opaco; não tente montá-lo ou interpretá-lo no cliente.
# Filtros avancados na rota legacy: prioridade alta, valor minimo, SLA vencido
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards?pipeline_id=1&priority[]=high&priority[]=urgent&value_min=5000&sla_exceeded=true" \
-H "api_access_token: YOUR_TOKEN" | jq .
# Filtrar por labels, agente e intervalo de datas
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards?labels[]=vip&agent_id=5&date_start=2026-01-01&date_end=2026-06-30" \
-H "api_access_token: YOUR_TOKEN" | jq .
# Cards sem responsavel
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards?agent_id=unassigned&pipeline_id=1" \
-H "api_access_token: YOUR_TOKEN" | jq .
# Canonica com status
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards?status=open&pipeline_id=1" \
-H "api_access_token: YOUR_TOKEN" | jq .{
"data": [
{
"id": 1,
"title": "Contrato Empresa XYZ",
"pipeline_id": 1,
"pipeline_stage": "1_qualificacao",
"position": 1,
"status": "open",
"contact_id": 10,
"owner_id": 5,
"lead_score": 78,
"conversation_display_id": 42,
"item_details": { "value": 15000.00 },
"active_sequence": { "id": 9, "name": "Onboarding", "status": "active", "current_step": 1, "total_steps": 3, "progress_percentage": 33 },
"created_at": "2026-01-10T10:00:00Z"
}
],
"meta": {
"limit": 50,
"offset": 0,
"has_more": false,
"next_cursor": null,
"stage_counts": { "1_lead": 12, "1_qualificacao": 18, "1_ganho": 12 }
}
}Cards pela rota /pipeline/cards
O CRUD de cards existe em dois caminhos com controllers diferentes. Os campos aceitos na escrita são os mesmos (uma única lista compartilhada, envolta em pipeline_card), e os dois respeitam a visibilidade do usuário. O que muda é paginação, formato de resposta e tratamento de erro:
| Aspecto | /pipeline_cards | /pipeline/cards |
|---|---|---|
| Paginação da listagem | limit (padrão 50, máx. 500) + offset ou cursor | page (padrão 1) + per_page (padrão 25, máx. 100) |
meta da listagem | limit, offset, has_more, next_cursor, stage_counts | current_page, per_page, total_pages, total_count |
| Card na listagem | Formato enxuto do quadro (inclui active_sequence, campos de SLA) | Mesmo objeto do detalhe (GET /pipeline/cards/{id}) |
| Filtros exclusivos | conversation_display_id, include_linked_conversations, exclude_id | owner_id, lead_score_category |
| Status de criação | 200 | 201 |
| Criação em pipeline fora do alcance | 404 antes de gravar (mesma régua de alcance da leitura) | apenas a policy de membro do pipeline (403 se o usuário não for membro) |
status no PATCH | 422 apontando para deal_status | não está na lista de campos aceitos — ignorado |
| Motivo no DELETE | não aceita | reason (ou discard_reason), gravado na lixeira |
| Sem permissão | Recusa da policy: 401 para usuário; 403 com reason: action_not_allowed_for_bot e allowed_actions para Agent Bot | Recusa da policy: 403 com { "error": "..." }, sem reason |
Agent Bot: a allowlist de permissões vem antes da policy
Nas duas rotas, uma requisição de Agent Bot passa primeiro pela checagem das permissões do bot. Se a rota não está disponível para bots, ou se falta a permissão, a resposta é 403 com reason: endpoint_not_available_to_bots ou reason: missing_permission (com required_permissions e granted_permissions) — antes de qualquer regra da tabela acima. Classifique pelo reason, não pelo status.
Mudança de etapa e desfecho não passam pelo PATCH
Em nenhuma das duas rotas o PATCH é o caminho para ganhar, perder ou reabrir um negócio: use POST /pipeline_cards/{id}/move_to_stage e os endpoints deal_status/*. Em /pipeline/cards, enviar status no PATCH não produz erro — o campo simplesmente não é gravado.
/api/v1/accounts/{account_id}/pipeline/cardsLista cards visíveis ao usuário com paginação por página. Aceita os filtros compartilhados da listagem (search, labels[], priority[], value_min/value_max, agent_id, date_start/date_end, status, sla_exceeded, stages[]) e os filtros abaixo. Parâmetro de query desconhecido retorna 422.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
page(query) | integer | Nao | Página (padrão 1) |
per_page(query) | integer | Nao | Itens por página (padrão 25, máximo 100) |
pipeline_id(query) | integer | Nao | Filtra por pipeline |
pipeline_stage(query) | string | Nao | Filtra por estágio ({pipeline_id}_{slug}) |
owner_id(query) | integer | Nao | Filtra por responsável |
contact_id(query) | integer | Nao | Filtra pelo contato primário do card |
lead_score_category(query) | string | Nao | hot, warm ou cold |
{
"data": [ { "id": 5, "title": "Contrato Empresa XYZ", "pipeline_id": 1, "pipeline_stage": "1_proposta", "status": "open" } ],
"meta": { "current_page": 1, "per_page": 25, "total_pages": 1, "total_count": 1 }
}/api/v1/accounts/{account_id}/pipeline/cardsCria um card. Body envolto em pipeline_card, com os mesmos campos aceitos por POST /pipeline_cards. Retorna 201 com o card; erro de validação retorna 422 com { errors }.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "pipeline_card": { "pipeline_id": 1, "pipeline_stage": "1_prospeccao", "title": "Novo lead", "contact_id": 10 } }'/api/v1/accounts/{account_id}/pipeline/cards/{id}Retorna o card (sem envelope). Card de pipeline que o usuário não vê, ou de outra conta, retorna 404.
/api/v1/accounts/{account_id}/pipeline/cards/{id}Atualiza campos do card. Body envolto em pipeline_card. PUT é aceito como alias.
item_details é mesclado, não substituído
Como na rota /pipeline_cards, o item_details enviado é mesclado sobre o valor atual; envie só as chaves que quer alterar. O histórico (item_details.history) só recebe acréscimos.
/api/v1/accounts/{account_id}/pipeline/cards/{id}Envia o card para a lixeira (soft delete). Restrito a administradores. Retorna 200 sem corpo.
Query ou Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
reason | string | Nao | Motivo do descarte, exibido na lixeira como discard_reason. Também aceito como discard_reason. |
Lixeira de Cards
Card excluído vai para a lixeira (discarded_at) e pode ser restaurado. Os três endpoints abaixo são restritos a administradores.
/api/v1/accounts/{account_id}/pipeline/cards/discardedLista os cards descartados, do mais recente para o mais antigo.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline_id(query) | integer | Nao | Restringe à lixeira desse pipeline. Pipeline inexistente ou de outra conta retorna 404. Sem ele, lista a lixeira da conta inteira. |
page(query) | integer | Nao | Página (padrão 1) |
per_page(query) | integer | Nao | Itens por página (padrão 25, máximo 100) |
{
"data": [
{
"id": 5,
"title": "Contrato Empresa XYZ",
"discarded_at": "2026-09-20T14:00:00Z",
"discarded_by": { "id": 3, "name": "Administrador" },
"discard_reason": "Duplicado",
"days_until_permanent_deletion": 23
}
],
"meta": { "current_page": 1, "per_page": 25, "total_pages": 1, "total_count": 1 }
}Contagem regressiva usa a retenção da conta
days_until_permanent_deletion considera o período de retenção configurado para a conta (padrão 30 dias), não um valor fixo.
/api/v1/accounts/{account_id}/pipeline/cards/{id}/restoreRestaura um card da lixeira e retorna o card restaurado.
/api/v1/accounts/{account_id}/pipeline/cards/{id}/permanently_deleteApaga o card definitivamente. Só funciona para card que já está na lixeira. Retorna 200 sem corpo.
Irreversível
Não há como recuperar o card depois desta chamada. Card que ainda não foi descartado retorna 422 com { "error": "Card must be discarded first before permanent deletion" } — descarte primeiro com DELETE /pipeline/cards/{id}.
Importar Cards (CSV)
Importe cards (deals) em lote a partir de um arquivo CSV. O processamento é assíncrono: a API valida e enfileira o import, e o job cria os cards em background. Ambos os endpoints exigem permissão de administrador.
/api/v1/accounts/{account_id}/pipeline/cards/templateBaixa um CSV modelo com os cabeçalhos esperados e uma linha de exemplo.
Formato do CSV modelo
A resposta é um arquivo text/csv (download direto — Content-Disposition: attachment, filename pipeline_cards_import_template.csv). A primeira linha contém os cabeçalhos e a segunda linha traz um exemplo preenchido.
Colunas (nessa ordem):
title— obrigatória, título do card.stage— estágio inicial do card. Aceita a chave completa (1_contato), o slug (contato) ou o nome legível sem distinção de caixa/acento (Contato,Negociação). Não reconhecido → primeira etapa do pipeline.description— descrição livre.expected_revenue— receita esperada (numérico).contact_identifier— identificador externo do contato.contact_email— email do contato.contact_phone— telefone do contato.
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/template" \
-H "api_access_token: YOUR_TOKEN" \
-o pipeline_cards_import_template.csv/api/v1/accounts/{account_id}/pipeline/cards/importEnvia um CSV para importar cards em lote (multipart/form-data). Processamento assíncrono.
multipart/form-data — campos obrigatórios
Envie como multipart/form-data. O arquivo CSV vai no campo import_file e o pipeline alvo no campo pipeline_id. O pipeline_id é resolvido dentro da conta autenticada — um pipeline de outra conta (ou ausente) retorna 422.
Validação do arquivo (limites)
O upload é validado antes de enfileirar: tamanho máximo de 10MB; content-type aceito text/csv, text/plain, application/csv ou application/vnd.ms-excel; e a primeira linha precisa conter o cabeçalho do template (coluna title). Arquivo fora dessas regras retorna 422.
Body (multipart/form-data)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
import_file(body) | file | Sim | Arquivo CSV no formato do template (cabeçalhos na 1ª linha). Máximo 10MB. |
pipeline_id(body) | integer | Sim | ID do pipeline alvo onde os cards serão criados (escopado à conta). |
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/import" \
-H "api_access_token: YOUR_TOKEN" \
-F "import_file=@pipeline_cards.csv" \
-F "pipeline_id=1"{
"message": "Importação iniciada. Os cards serão criados em segundo plano.",
"import_id": 42
}{ "error": "Pipeline obrigatorio para importar cards." }Exportar Cards (CSV)
Exporta os cards da conta como um arquivo CSV, respeitando os mesmos filtros da listagem legacy /pipeline_cards. O resultado é escopado à conta autenticada (a visibilidade por pipeline do usuário também se aplica).
/api/v1/accounts/{account_id}/pipeline/cards/exportExporta os cards filtrados como CSV (download direto). Aceita os query params da listagem legacy /pipeline_cards.
Filtros aceitos
Os mesmos filtros da rota legacy de listagem se aplicam (query params): pipeline_id, pipeline_stage, conversation_display_id, contact_id, labels[], priority[], value_min/value_max, agent_id, date_start/date_end, status, sla_exceeded, stages[] e search. Sem filtros, exporta todos os cards visíveis da conta.
A resposta é um arquivo text/csv (Content-Disposition: attachment, filename pipeline_cards_export_<timestamp>.csv, com o timestamp no fuso da conta).
Filtros exclusivos da rota canônica
owner_id e lead_score_category são filtros exclusivos da listagem canônica e não são aceitos pelo export. Para filtrar por responsável no CSV, use agent_id.
Limite de linhas e proteção anti-formula
O export síncrono é limitado a 20.000 cards. Acima disso a API retorna 422 — aplique filtros para reduzir o resultado.
Células de texto que começam com =, @, + ou - (e não são números/telefones) são prefixadas com apóstrofo (') para neutralizar injeção de fórmula em planilhas (CSV injection).
Colunas do CSV exportado
id, title, pipeline_id, stage, status, priority, value, currency, owner_name, contact_name, contact_email, contact_phone, conversation_display_id, labels, lead_score, lead_score_category, created_at, won_at, lost_at, deadline. Datas em ISO 8601 no fuso da conta; labels são separadas por |.
# Exportar cards abertos de um pipeline que correspondem a busca textual
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/export?pipeline_id=1&status=open&search=Acme" \
-H "api_access_token: YOUR_TOKEN" \
-o pipeline_cards_export.csv/api/v1/accounts/{account_id}/pipeline_cardsCria um novo card (deal) no pipeline.
Como obter o pipeline_stage
O campo pipeline_stage é uma string no formato {pipeline_id}_{slug} (ex: "1_prospeccao"). Use GET /pipelines/{id}/stages para listar os IDs reais dos estágios do seu pipeline antes de criar cards.
Body envolto em pipeline_card
Envie sempre os campos dentro do wrapper pipeline_card. A rota legacy /pipeline_cards tolera campos no nível raiz (são re-embrulhados automaticamente via wrap_parameters e o card é criado normalmente), mas a rota canônica POST /pipeline/cards é estrita: sem o wrapper ela retorna 422 (param is missing: pipeline_card). Use sempre o wrapper para um comportamento consistente entre as duas rotas.
Body (pipeline_card)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
title | string | Nao | Título do deal no nível raiz. Opcional — se omitido, o card usa item_details.title (ou fica null). |
pipeline_id | integer | Sim | ID do pipeline |
pipeline_stage | string | Sim | ID do estágio no formato "{pipeline_id}_{slug}" (ex: "1_prospeccao"). Obtenha via GET /pipelines/{id}/stages. Precisa ser um estágio comum: nascer em etapa de Ganho/Perda retorna 422 (veja abaixo). |
position | integer | Nao | Posição do card no estágio |
contact_id | integer | Nao | ID do contato associado (resolvido dentro da conta autenticada) |
conversation_display_id | integer | Nao | Display ID da conversa associada (per-account, não ID global) |
owner_id | integer | Nao | ID do responsável |
timer_started_at | string | Nao | Início do timer (ISO 8601) |
timer_duration | integer | Nao | Duração acumulada do timer |
scheduled_at | string | Nao | Data/hora agendada (ISO 8601; rota legacy) |
expected_revenue | number | Nao | Receita esperada do deal |
currency | string | Nao | Código de moeda com exatamente 3 letras, armazenado em maiúsculas (ex: BRL, USD). O backend valida o formato, não a existência em um catálogo oficial. |
deadline | string | Nao | Data prevista de fechamento (ISO 8601) |
forecast_close_date | string | Nao | Previsão de fechamento (ISO 8601; rota legacy) |
description | string | Nao | Descrição do card |
priority | string | Nao | Prioridade do card |
source | string | Nao | Origem do card (rota legacy) |
item_details | object | Nao | JSONB freeform. value e currency são espelhos legados; prefira expected_revenue e currency no nível raiz. Em offers[], url precisa ser um endereço http ou https absoluto (com host) — outros esquemas retornam 422, porque o valor vira link no painel. |
custom_attributes | object | Nao | Atributos personalizados |
qualification_checklist | object | Nao | Checklist de qualificação |
tags | string[] | Nao | Tags do card |
Schemas diferentes nas duas rotas de cards
A tabela acima descreve a rota legacy /pipeline_cards. A rota canônica /pipeline/cards também aceita os campos comuns, adiciona probability e company_id, mas não aceita os campos legacy scheduled_at, forecast_close_date e source. Não reutilize um payload entre as duas rotas sem considerar essa diferença.
Um card não nasce fechado
pipeline_stage aqui precisa apontar para um estágio comum. Criar um card diretamente em uma etapa de Ganho ou Perda retorna 422: esse atalho gravava o card como fechado sem passar pelo fechamento real — sem valor fechado, sem won_at/lost_at e sem o registro da oportunidade no ledger, deixando a receita divergente do que o board mostra.
Para importar um deal já fechado, crie o card em um estágio comum e feche-o em seguida com POST .../deal_status/mark_won ou POST .../deal_status/mark_lost.
{
"errors": {
"pipeline_stage": [
"can only reach a won/lost stage through the deal endpoints (mark_won / mark_lost / reopen)"
]
}
}# Primeiro obtenha os IDs dos estágios:
# curl -s ".../api/v1/accounts/1/pipelines/1/stages" -H "api_access_token: TOKEN"
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pipeline_card": {
"title": "Contrato Empresa XYZ",
"pipeline_id": 1,
"pipeline_stage": "1_prospeccao",
"contact_id": 10,
"owner_id": 5,
"expected_revenue": 15000.00,
"currency": "BRL",
"item_details": {
"title": "Contrato Empresa XYZ"
},
"deadline": "2026-03-15T00:00:00Z"
}
}'/api/v1/accounts/{account_id}/pipeline_cards/{id}Retorna os detalhes e o score de um card. A timeline não faz parte desta resposta; consulte GET /pipeline/cards/{id}/timeline.
/api/v1/accounts/{account_id}/pipeline_cards/{id}Atualiza dados de um card. Body envolto em pipeline_card.
Body (pipeline_card)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
title | string | Nao | Título do card |
pipeline_stage | string | Nao | Estágio (formato "{pipeline_id}_{slug}"). Para mover entre estágios prefira POST /move_to_stage. Entrar ou sair de uma etapa de Ganho/Perda por aqui retorna 422 (veja abaixo). |
pipeline_id | integer | Nao | ID do pipeline |
position | integer | Nao | Posição do card no estágio |
owner_id | integer | Nao | ID do responsável |
contact_id | integer | Nao | ID do contato |
conversation_display_id | integer | Nao | Display ID da conversa associada |
timer_started_at | string | Nao | Início do timer (ISO 8601) |
timer_duration | integer | Nao | Duração acumulada do timer |
scheduled_at | string | Nao | Data/hora agendada (ISO 8601; rota legacy) |
expected_revenue | number | Nao | Receita esperada (coluna do card) |
currency | string | Nao | Código de moeda com exatamente 3 letras, armazenado em maiúsculas (ex: BRL, USD). Valida o formato, não um catálogo oficial. |
deadline | string | Nao | Data prevista de fechamento (ISO 8601) |
forecast_close_date | string | Nao | Data de previsão de fechamento (ISO 8601) |
description | string | Nao | Descrição do card |
priority | string | Nao | Prioridade do card |
source | string | Nao | Origem do card (rota legacy) |
item_details | object | Nao | JSONB freeform. value, currency e deadline_at são espelhos/aliases legados. Faz deep-merge com o valor atual — envie apenas as chaves a alterar. |
custom_attributes | object | Nao | Atributos personalizados (JSONB) |
qualification_checklist | object | Nao | Checklist de qualificação |
tags | string[] | Nao | Tags do card |
Status usa endpoints de transição dedicados
O campo pipeline_card.status não é aceito pelo PATCH genérico. Ele é removido dos parâmetros permitidos, portanto a requisição pode retornar 200 sem alterar o status. Use exclusivamente POST .../deal_status/mark_won, POST .../deal_status/mark_lost ou POST .../deal_status/reopen para executar a transição completa.
Fechar e reabrir um deal não passa pelo PATCH
Bloquear apenas status não bastava: mudar o pipeline_stage já fechava o deal por tabela, pulando tudo que o fechamento garante (valor fechado, won_at/lost_at, lost_reason e o registro da oportunidade no ledger). Por isso o PATCH genérico agora recusa a transição nos dois sentidos, com 422:
- Entrar em uma etapa de Ganho/Perda (inclusive quando a mudança vem de trocar o
pipeline_idmantendo a mesma chave de estágio) — usePOST .../deal_status/mark_wonouPOST .../deal_status/mark_lost. - Sair de uma etapa de Ganho/Perda com o deal ainda fechado — use
POST .../deal_status/reopen. Arrastar o card de volta pelo PATCH o mostrava aberto no board enquantostatus,won_ate a oportunidade continuavam fechados.
POST .../move_to_stage não foi afetado: ele detecta a etapa de destino e executa o fechamento completo por conta própria — e, ao tirar um card fechado de Ganho/Perda para uma etapa comum, reabre o negócio. Continua sendo o caminho recomendado para mover cards, inclusive de e para etapas terminais.
{
"errors": {
"pipeline_stage": [
"can only reach a won/lost stage through the deal endpoints (mark_won / mark_lost / reopen)"
]
}
}Campos financeiros canônicos e espelhos legados
Use os campos raiz expected_revenue e currency como fonte canônica. O backend mantém os espelhos legados em item_details sincronizados automaticamente.
expected_revenue— coluna numérica canônica do card (receita esperada). Use este para o valor previsto do deal.currency— código com exatamente três letras, armazenado em maiúsculas. A validação confirma o formato, mas não consulta um catálogo oficial de moedas.item_details.valueeitem_details.currency— espelhos legados em JSONB. Quando o payload envia campos raiz e espelhos com valores diferentes, os campos raiz explícitos têm precedência e sobrescrevem os espelhos.won_value— preenchido apenas ao fechar o deal viaPOST .../deal_status/mark_won(valor fechado).
Se item_details.offers estiver presente, todas as ofertas devem usar a mesma moeda canônica do card. Valores financeiros malformados, código de moeda fora do formato de três letras ou ofertas com moedas diferentes retornam 422 Unprocessable Entity.
curl -X PATCH "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards/5" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pipeline_card": {
"title": "Contrato Empresa XYZ - revisado",
"owner_id": 5,
"expected_revenue": 18000,
"currency": "BRL"
}
}'/api/v1/accounts/{account_id}/pipeline_cards/{id}Remove um card (soft delete).
Soft delete escopado à conta
O id é resolvido dentro da conta autenticada(Current.account.pipeline_cards.find). Não é um ID global — tentar deletar um card de outra conta retorna 404. A operação é um soft delete (discarded_at); o card pode ser visto em GET /pipeline/cards/discarded e restaurado via POST /pipeline/cards/{id}/restore. A remoção definitiva exige um passo separado e explícito (DELETE /pipeline/cards/{id}/permanently_delete, só para cards já descartados).
Lixeira por pipeline: GET /pipeline/cards/discarded é admin-only e aceita o parâmetro opcional pipeline_id(filtra a lixeira daquele pipeline; pipeline_id inválido/de outra conta retorna 404). Sem pipeline_id, lista a lixeira da conta inteira. Cada card retorna discarded_at, discarded_by, discard_reason e days_until_permanent_deletion (contagem regressiva até a exclusão definitiva, respeitando a retenção da conta — default 30 dias).
Contatos e Conversas adicionais (multi-vínculo)
Além do contato e da conversa primários do card (contact_id / conversation_display_id), um card pode ter contatos e conversas adicionais vinculados. Isso permite representar um deal com vários envolvidos (ex: decisor + influenciador) ou várias conversas relacionadas. Os vínculos primários não mudam — estes endpoints apenas adicionam/removem vínculos extras (join aditivo, many-to-many).
Aditivo e retrocompatível — o vínculo primário não muda
O contato primário continua em contact_id/contact e a conversa primária em conversation_display_id. Os vínculos adicionais aparecem em dois novos campos aditivos do recurso pipeline_card: additional_contacts e additional_conversations. O vínculo primário é deduplicado para fora desses arrays (nunca aparece duas vezes).
Novos campos no recurso do card
{
"id": 5,
"contact_id": 10,
"conversation_display_id": 42,
"additional_contacts": [
{
"id": 3,
"contact_id": 27,
"name": "Joao Comprador",
"email": "joao@empresa.com",
"phone_number": "+5511999999999",
"avatar_url": "",
"role": "decisor"
}
],
"additional_conversations": [
{ "id": 8, "conversation_display_id": 57 }
]
}id do vínculo != id do recurso
Em additional_contacts/additional_conversations, o campo id é o ID do registro de vínculo (join), não o contact_id nem o conversation_display_id. Use esse id de vínculo nas rotas DELETE abaixo.
Contatos adicionais
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/contactsVincula um contato adicional (não-primário) ao card. Requer permissão de update no card.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
contact_id | integer | Sim | ID do contato a vincular (resolvido dentro da conta autenticada) |
role | string | Nao | Rótulo livre do papel do contato no deal (ex: "decisor", "influenciador") |
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/5/contacts" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "contact_id": 27, "role": "decisor" }'{
"data": {
"id": 3,
"contact_id": 27,
"name": "Joao Comprador",
"email": "joao@empresa.com",
"phone_number": "+5511999999999",
"avatar_url": "",
"role": "decisor"
}
}Erros
404 se o card ou o contato não existir na conta; 422 se o contato já estiver vinculado a este card (seja como contato primário ou como adicional já existente). Entradas inválidas nunca retornam 500.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/contacts/{id}Remove um vínculo de contato adicional. Não afeta o contato primário (contact_id) do card.
O {id} é o ID do vínculo
O {id} na rota é o id do registro de vínculo (o campo id de um item de additional_contacts), não o contact_id. Retorna 204 No Content em sucesso; 404 se o vínculo não existir neste card.
curl -X DELETE "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/5/contacts/3" \
-H "api_access_token: YOUR_TOKEN"Conversas adicionais
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/conversationsVincula uma conversa adicional (não-primária) ao card. Requer permissão de update no card.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
conversation_display_id | integer | Sim | Display ID da conversa a vincular (per-account, não ID global) |
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/5/conversations" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "conversation_display_id": 57 }'{
"data": {
"id": 8,
"conversation_display_id": 57
}
}Erros
404 se o card ou a conversa não existir na conta; 422 se a conversa já estiver vinculada a este card (seja como conversa primária ou como adicional já existente). Entradas inválidas nunca retornam 500.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/conversations/{id}Remove um vínculo de conversa adicional. Não afeta a conversa primária (conversation_display_id) do card.
O {id} é o ID do vínculo
O {id} na rota é o id do registro de vínculo (o campo id de um item de additional_conversations), não o conversation_display_id. Retorna 204 No Content em sucesso; 404 se o vínculo não existir neste card.
curl -X DELETE "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/5/conversations/8" \
-H "api_access_token: YOUR_TOKEN"Mover Card entre Estágios
/api/v1/accounts/{account_id}/pipeline_cards/{id}/move_to_stageMove um card para outro estágio do pipeline.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline_stage | string | Sim | Identificador do estágio destino no formato "{pipeline_id}_{stage_slug}" (ex: "2_qualificao"). Use GET /pipelines/{id}/stages para obter os identificadores disponíveis. |
lost_reason | string | Nao | Motivo da perda quando o destino é um estágio terminal de perda |
won_value | number | Nao | Valor fechado quando o destino é um estágio terminal de ganho |
won_note | string | Nao | Nota do ganho quando o destino é um estágio terminal de ganho |
expected_version | integer | Nao | O stage_version que você leu do card. O movimento só é aplicado se o card ainda estiver nessa versão; senão, 409 e nada muda. Obrigatório para Agent Bot (sem ele: 422 com reason expected_version_required); opcional para usuário. |
Esta rota lida com etapas terminais
Diferente do PATCH /pipeline_cards/{id}, este endpoint aceita um destino de Ganho ou Perda: ele identifica o tipo da etapa e executa o fechamento completo internamente (o mesmo caminho de mark_won/mark_lost), aproveitando won_value, won_note e lost_reason quando enviados. É o caminho recomendado para mover cards por API, inclusive para fechar um deal em uma única chamada.
O caminho inverso, para usuário autorizado: mover um card que está ganho ou perdido para uma etapa comum reabre o negócio e já o coloca na etapa pedida (200) — mesmo efeito de deal_status/reopen (limpa won_at/lost_at, valor e motivo, e registra a reabertura na trilha). Não é preciso chamar reopen antes. O PATCH genérico continua recusando essa transição com 422.
Agent Bot não reabre negócio movendo o card (desde a 4.18.0.1)
Reabrir um negócio fechado é decisão humana. Se um Agent Bot move um card ganho ou perdido para uma etapa comum, a resposta é 403 com reason: action_not_allowed_for_bot e o card não é alterado — valor, datas e motivo do desfecho ficam como estavam, qualquer que seja a lista de ações permitidas do bot. Reabrir de propósito é um gesto separado, feito pelo endpoint explícito POST /pipeline/cards/{id}/deal_status/reopen — veja o aviso sobre reenvio nesse endpoint antes de usá-lo numa integração.
A autorização vem antes da comparação de versão. Um bot fazendo um gesto que não pode fazer recebe 403 mesmo com expected_version obsoleto — portanto um 403 não afirma nada sobre a versão que você enviou. Não reautentique (o token é válido): pule a ação e siga. Usuário humano sem permissão continua recebendo 401 nesta rota; a distinção é pelo tipo de principal, não pela rota.
A comparação de versão também cobre a reabertura: um movimento de reabertura com expected_version obsoleto retorna 409 com reason: stage_version_conflict e nada é reaberto.
Classifique a resposta por reason, nunca pelo texto de error
403·action_not_allowed_for_bot— ação fora do que o bot pode fazer; trazallowed_actions(o que ele pode fazer de fato em cards).409·stage_version_conflict— alguém moveu o card depois da sua leitura; o card não mudou. Trazexpected_version,current_versionecurrent_stagecomo diagnóstico. Releia, decida de novo e tente — a nova tentativa pode passar.409·card_already_closedecycle_already_recorded— o desfecho já aconteceu; repetir o mesmo movimento nunca vai passar. Ambos trazemrequires_fresh_read: true.422·expected_version_required(bot sem versão) einvalid_expected_version(valor que não é inteiro não negativo) — a requisição nem foi avaliada. Trazemrequires_fresh_read: truee, de propósito, não trazemcurrent_version: releia o card com um GET e envie a versão que você leu. Nunca reenvie com uma versão tirada de um corpo de erro.
A etapa terminal é identificada pelas flags is_won_stage/is_lost_stage do estágio, nunca pelo nome. Toda resposta de sucesso traz o stage_version atual do card.
{
"error": "This agent bot is not allowed to perform this action on pipeline cards",
"reason": "action_not_allowed_for_bot",
"allowed_actions": ["cards_move"]
}curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards/5/move_to_stage" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "pipeline_stage": "1_qualificacao" }'
# Destino terminal: o fechamento acontece dentro da propria rota.
# Pegue o identificador real da etapa de Ganho em GET /pipelines/1/stages
# (o estagio com is_won_stage = true).
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards/5/move_to_stage" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "pipeline_stage": "<id da etapa de Ganho>", "won_value": 18000 }'/api/v1/accounts/{account_id}/pipeline_cards/reorderReordena cards dentro de um estágio.
pipeline_id define um único escopo
Envie pipeline_id no nível raiz, que é o formato canônico. Se ele for omitido, o backend aceita apenas o fallback legado em positions[0].pipeline_id; sem nenhum dos dois, a API retorna 400. O valor escolhido vira o escopo global do reorder: cards descartados e cards de outros pipelines são ignorados, e valores de pipeline_id nas demais entradas não são processados como escopos independentes.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
pipeline_id | integer | Nao | Campo raiz canônico e recomendado. Define o único pipeline cujos cards serão reordenados. |
positions | array | Sim | Array de objetos { id, position, pipeline_stage }. Entradas sem pipeline_stage são ignoradas. |
positions[0].pipeline_id | integer | Nao | Fallback legado, consultado somente quando pipeline_id raiz está ausente. O valor passa a escopar todo o array. |
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline_cards/reorder" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pipeline_id": 1,
"positions": [
{ "id": 5, "position": 0, "pipeline_stage": "1_qualificacao" },
{ "id": 9, "position": 1, "pipeline_stage": "1_qualificacao" }
]
}'Status do Deal
/api/v1/accounts/{account_id}/pipeline/cards/{id}/deal_status/mark_wonMarca o deal como ganho. Requer que o pipeline tenha etapas Ganho/Perdido habilitadas.
Body (opcional)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
won_value | number | Nao | Valor final fechado (numérico). Valor inválido retorna 422. |
won_note | string | Nao | Nota sobre o ganho |
winning_offer_index | integer | Nao | Índice da oferta vencedora (quando o card tem ofertas em item_details) |
pipeline_product_id | integer | Nao | Produto do catálogo que foi vendido. É esse vínculo que permite medir receita por produto. |
quantity | number | Nao | Quantidade vendida. Só faz sentido junto de pipeline_product_id. |
unit_value | number | Nao | Preço efetivamente cobrado por unidade — o preço do catálogo é apenas o ponto de partida. |
items | array | Nao | Vender vários produtos na mesma negociação (1 a 50 itens). Mutuamente exclusivo com pipeline_product_id/quantity/unit_value acima — use um formato ou outro, não os dois. |
Vendendo mais de um produto no mesmo fechamento
Envie items — um array de objetos { pipeline_product_id, quantity, unit_value, total_value, title, note, custom_attributes } — em vez dos campos escalares acima, para registrar vários produtos na mesma venda, cada um com sua própria quantidade e valor. quantity tem padrão 1 (até 3 casas decimais, deve ser positiva); total_value é obrigatório quando unit_value está ausente, e quando os dois vêm juntos total_value precisa ser igual a quantity * unit_value. pipeline_product_id por item segue a mesma recusa por escopo/inatividade/indisponibilidade descrita abaixo.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/5/deal_status/mark_won" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"won_value": 15000,
"won_note": "Fechado no plano anual",
"pipeline_product_id": 7,
"quantity": 2,
"unit_value": 7500
}'Resposta uniforme
mark_won, mark_lost e reopen retornam o mesmo shape: { id, status, pipeline_stage, won_at, won_value, won_note, lost_at, lost_reason, item_details }. Card já ganho retorna 409.
pipeline_product_id e unit_value vêm NULL com vários itens
O bloco opportunityda resposta (ver "Razão de Vendas do Card" abaixo) sempre traz um array items com cada linha vendida. Os campos soltos pipeline_product_id e unit_value no Nível RAIZ do opportunity são apenas um eco de compatibilidade com o formato de produto único: eles vêm preenchidos SOMENTE quando a venda tem EXATAMENTE UM item, e vêm null quando a venda tem 0 itens (valor livre, sem produto) ou 2 ou mais. Já quantity nesse mesmo nível NUNCA é null — é sempre a SOMA da quantidade de todos os itens. Não trate pipeline_product_id: null como "produto não vinculado" sem antes checar se items tem mais de uma linha.
O produto é recusado fora do seu escopo
pipeline_product_id retorna 422 quando o produto pertence a outra conta, está desativado, ou não está disponível no funil daquele card. A recusa é proposital: o razão de vendas é append-only, então um vínculo errado sobreviveria à correção. Sem esse campo o fechamento continua funcionando — a venda apenas fica sem produto vinculado e não entra na medição por produto.
Reabrir e ganhar de novo registra uma SEGUNDA venda
O valor de um negócio já fechado não muda em silêncio: mark_won em card ganho retorna 409. Reabrir (reopen) e ganhar novamente registra uma nova venda — o comportamento correto para recompra —, não substitui a anterior. Para corrigir um lançamento errado, estorne-o em POST .../pipeline/opportunities/{id}/void.
/api/v1/accounts/{account_id}/pipeline/cards/{id}/deal_status/mark_lostMarca o deal como perdido.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
lost_reason | string | Nao | Motivo da perda |
/api/v1/accounts/{account_id}/pipeline/cards/{id}/deal_status/reopenReabre um deal marcado como ganho ou perdido.
Reabrir é destrutivo — cuidado com reenvio
Este endpoint está disponível para usuários autorizados e para Agent Bot com pipeline_manage, de propósito: quem registrou uma perda ou um ganho por engano precisa poder desfazer. Mas a reabertura limpa won_at/lost_at, apaga o valor e o motivo do desfecho e cancela as sequências ativas do card.
O valor sai do card, mas não se perde: cada ganho anterior continua no histórico de oportunidades do card (GET .../pipeline/cards/{card_id}/opportunities, que exige o módulo Pipeline Opportunities ativo). Ganhar de novo registra outra oportunidade, sem estornar a anterior — para corrigir um ganho errado, estorne a oportunidade.
Um retry, um replay de fila ou uma compensação que reenvie esta chamada sobre um card já fechado apaga o desfecho e recebe 200, sem erro. Trate a chamada como não idempotente em efeito: leia o card antes e não a reenvie por padrão.
Num card que já está aberto a chamada não faz nada: responde 200 com status: open, sem mover o card, sem cancelar sequências e sem registrar reabertura na trilha.
A reabertura como efeito colateral de um movimento é outra coisa: um Agent Bot que move um card de Ganho/Perdido para uma etapa comum recebe 403 com reason: action_not_allowed_for_bot e o card não é alterado.
Timeline do Card
/api/v1/accounts/{account_id}/pipeline/cards/{id}/timelineRetorna a timeline de atividades do card.
{
"timeline": [
{
"date": "2026-01-15",
"activities": [
{
"id": 1780508401,
"type": "history_event",
"event_type": "stage_changed",
"details": {
"user": { "id": 5, "name": "Maria Santos", "avatar_url": "" },
"old_stage": "1_prospeccao",
"new_stage": "1_qualificacao"
},
"user": { "id": 5, "name": "Maria Santos", "avatar_url": "" },
"created_at": "2026-01-15T14:30:00Z",
"timestamp": "2026-01-15T14:30:00Z"
}
]
}
]
}Anexos do Card
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachmentsLista anexos de um card.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachmentsAdiciona um anexo ao card.
Body (multipart/form-data)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
attachment(body) | file | Sim | Arquivo a anexar, com no máximo 25 MB |
Tipos de arquivo aceitos
Imagens JPEG, PNG, GIF, WebP e SVG; PDF, TXT e CSV; documentos Word, Excel e PowerPoint; e arquivos ZIP. O servidor valida o tipo real do arquivo, não apenas o header enviado pelo cliente.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/5/attachments" \
-H "api_access_token: YOUR_TOKEN" \
-F "attachment=@/path/to/proposta.pdf"/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachments/{id}Remove um anexo. Operação restrita a administradores.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachments/{id}Baixa o arquivo do anexo (download direto, com o nome e o content-type originais). O download é registrado na trilha do card.
curl -OJ "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/5/attachments/31" \
-H "api_access_token: YOUR_TOKEN"Arquivos de notas
Arquivos anexados a uma nota do card são enviados à parte e depois referenciados pelo id na nota salva em item_details.notes[].attachments[]. Um arquivo enviado e nunca referenciado por uma nota é descartado automaticamente após cerca de 24 horas. Os dois endpoints exigem permissão de edição no card.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/note_attachmentsEnvia um arquivo para uso em nota (multipart/form-data).
Body (multipart/form-data)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
attachment(body) | file | Sim | Arquivo com no máximo 10 MB. Aceita JPEG, PNG, GIF, WebP, PDF, TXT, CSV, Word, Excel e ZIP. |
{
"id": 88,
"attachment_url": "https://chat.seudominio.com/rails/active_storage/...",
"message": "Attachment uploaded successfully"
}/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/note_attachments/{id}Remove um arquivo de nota. Retorna { message: 'Attachment deleted successfully' }.
Quem pode remover
Arquivo já usado numa nota salva pode ser removido por quem edita o card. Arquivo ainda não referenciado só pode ser removido por quem o enviou ou por um administrador sem função personalizada; os demais recebem 403.
Sequências de Atividade no Card
Inscreve um card numa sequência de atividades (cadência automatizada de etapas, definida previamente em pipeline/activity_sequences). Sequências estão incluídas em toda licença NooviChat válida; o acesso depende das permissões do usuário e da configuração operacional da conta. Um card só pode ter uma sequência ativa/pausada da mesma definição por vez (422 em duplicidade).
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequencesLista as sequências inscritas no card.
{
"data": [
{
"id": 14,
"status": "active",
"current_step": 1,
"total_steps": 4,
"progress_percentage": 25,
"definition": { "id": 3, "name": "Cadência de prospeccao" },
"next_step_at": "2026-06-15T12:00:00-03:00",
"steps_log": [],
"created_at": "2026-06-13T09:00:00-03:00",
"updated_at": "2026-06-13T09:00:00-03:00"
}
]
}/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequencesInicia uma sequência no card.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
definition_id | integer | Sim | ID de uma activity_sequence ATIVA da conta |
Respostas
201 com a sequência criada;404 se a definição não existir/estiver inativa; 422 se já houver uma sequência ativa/pausada da mesma definição no card.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/external_startPonto de entrada para integrações externas (ex: n8n). Inicia uma sequência com contexto controlado.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
definition_id | integer | Sim | ID de uma activity_sequence ativa |
context | object | Nao | Objeto de até 10 KB. Chaves aceitas: trigger_source, metadata, external_id, source_url e notes. Chaves desconhecidas são descartadas; valor não-objeto retorna 400. |
Respostas desta entrada externa
Retorna 201 quando inicia. Definição inexistente ou inativa retorna 422 nesta rota (a rota normal POST .../sequences usa 404 para o mesmo caso); duplicidade ativa/pausada retorna 422.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/{id}/pausePausa a execução ativa da sequência. 422 se não houver execução ativa.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/{id}/resumeRetoma uma sequência pausada. 422 se não houver execução pausada.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/{id}/complete_stepAvança manualmente para a próxima etapa da sequência. 422 se não houver execução ativa ou se não restarem etapas.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/{id}Cancela a sequência no card (marca como cancelled). Retorna 200.
/api/v1/accounts/{account_id}/pipeline/sequence_analyticsPainel de sequências da conta: série diária de iniciadas/concluídas, sequências ativas, concluídas hoje, duração média e as definições mais usadas. Dias contados no fuso da conta.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
days_back(query) | integer | Nao | Tamanho da série diária, em dias (padrão 7, máximo 90) |
{
"data": {
"summary": {
"2026-09-27": { "started": 4, "completed": 2, "failed": 0 },
"2026-09-26": { "started": 3, "completed": 1, "failed": 1 }
},
"active_sequences_count": 12,
"completed_today": 2,
"avg_completion_days": 6.5,
"top_definitions": [ { "id": 3, "name": "Onboarding", "starts": 18 } ]
}
}O recorte depende de quem pergunta
Administrador vê a conta inteira. Agente vê só as sequências de cards que ele enxerga; nesse caso a série diária conta iniciadas e concluídas, e failed vem sempre 0. avg_completion_days é a média das 100 conclusões mais recentes; top_definitions considera os últimos 30 dias. Se o módulo de sequências estiver desativado na conta, a resposta é 403 com code: pipeline_sequences_disabled.
Operações em Lote
Operação administrativa em massa — escopo de conta
bulk_assign exige administrador e opera apenas sobre cards da conta autenticada (Current.account.pipeline_cards.where(id: item_ids)). IDs de cards de outras contas são silenciosamente ignorados — não há vazamento cross-tenant. O lote é limitado a 200 cards por requisição; passar mais retorna 400. A autorização vem antes dessa validação: quem não é administrador recebe 403, qualquer que seja o corpo. Esta operação apenas reatribui responsável — não remove nem move cards.
/api/v1/accounts/{account_id}/pipeline/cards/bulk_assignAtribui responsável para vários cards de uma vez (max 200).
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
item_ids | array | Sim | IDs dos cards (max 200, escopados à conta autenticada) |
owner_id | integer | Nao | ID do novo responsável. Se distribution não for usado, omitir owner_id desatribui os cards. |
distribution | string | Nao | round_robin ou workload_balanced — distribui entre agentes/administradores com disponibilidade online ou busy. Se omitido, usa owner_id. |
Omitir owner_id e distribution desatribui
Para distribuição automática, envie explicitamente distribution. Se os dois campos forem omitidos, o backend atualiza os cards com responsável nulo.
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/bulk_assign" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "item_ids": [5, 9, 12], "owner_id": 3 }'/api/v1/accounts/{account_id}/pipeline/cards/{id}/assignAtribui ou remove o responsável de um card. Para Agent Bot estreitado por pipeline_actions, exige a ação cards_assign (lista vazia libera todas).
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
owner_id | integer | null | Sim | ID do responsável; null explícito remove o responsável. Sem a chave (e sem o alias user_id) o pedido é 422. |
notify | boolean | Nao | Enviar notificação ao novo responsável |
/api/v1/accounts/{account_id}/pipeline/bulk_actions/deleteEnvia vários cards para a lixeira (soft delete, reversível), um a um, com o mesmo efeito do DELETE individual. Restrito a administradores.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
card_ids | array | Sim | IDs dos cards (máx. 500). IDs fora da conta ou de pipelines que você não vê são ignorados. |
pipeline_stage | string | Nao | Age só sobre os cards da lista que estão nesse estágio |
reason | string | Nao | Motivo do descarte, gravado em cada card |
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/bulk_actions/delete" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "card_ids": [5, 9, 12], "reason": "Leads duplicados" }'{ "success": true, "deleted": 3, "failed": 0, "failed_items": [] }/api/v1/accounts/{account_id}/pipeline/bulk_actions/set_priorityDefine a prioridade de vários cards de uma vez. Exige permissão de edição de cards (administrador, ou agente sem função personalizada ou com pipeline_manage).
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
card_ids | array | Sim | IDs dos cards (máx. 500), escopados à conta e aos pipelines visíveis |
priority | string | Sim | none, low, medium, high ou urgent. Outro valor retorna 422 com a lista permitida. |
pipeline_stage | string | Nao | Age só sobre os cards da lista que estão nesse estágio |
{ "success": true, "updated": 2, "updated_ids": [5, 9], "failed": 0, "failed_items": [] }Automações do Pipeline
Crie workflows automatizados que executam ações quando cards mudam de estágio, atingem determinado score ou atendem condições específicas.
Quem pode alterar automações
Criar, atualizar, importar, duplicar, criar a partir de template e simular (dry_run) exige administrador ou agente com a permissão pipeline_manage. Remover, executar, exportar e ver ou rotacionar a credencial de webhook exige administrador. Usuários autenticados podem consultar as automações globais e as dos pipelines que conseguem visualizar.
/api/v1/accounts/{account_id}/pipeline/automationsLista as automações globais e as dos pipelines visíveis ao usuário. Retorna um array (cada automação inclui stats agregado).
[
{
"id": 1,
"name": "Notificar ao entrar em Negociação",
"description": "Avisa o responsável quando um card chega na negociação",
"active": true,
"trigger_type": "event",
"pipeline_id": 1,
"trigger": { "event": "stage_change", "to_stage": "1_negociacao" },
"conditions": [],
"actions": [{ "type": "send_notification", "to": "owner" }],
"flow": { "nodes": [], "connections": [], "viewport": { "x": 0, "y": 0, "zoom": 1 } },
"stats": {
"total_executions": 23,
"success_count": 22,
"failure_count": 1,
"success_rate": 96
}
}
]/api/v1/accounts/{account_id}/pipeline/automations/{id}Retorna uma automação (sem envelope), no mesmo formato de cada item da listagem, mas sem o bloco stats. Automação de pipeline que o usuário não vê retorna 404.
Segredos só para administrador
Valores sensíveis guardados na configuração (por exemplo, cabeçalhos de autenticação de um nó de requisição HTTP) só aparecem por extenso para administradores; para os demais usuários vêm mascarados.
/api/v1/accounts/{account_id}/pipeline/automationsCria uma nova automação. Body envolto em pipeline_automation.
Body envolto em pipeline_automation
Os campos devem estar dentro do wrapper pipeline_automation. trigger_type aceita: event, webhook, scheduled ou manual.
Body (pipeline_automation)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
name | string | Sim | Nome da automação |
description | string | Nao | Descrição |
active | boolean | Nao | Se a automação está ativa |
trigger_type | string | Nao | event | webhook | scheduled | manual |
pipeline_id | integer | Nao | ID do pipeline alvo |
trigger | object | Nao | Configuração do gatilho legado (ex: { event, to_stage }). Complementar: não substitui o flow. |
conditions | array | Nao | Array de condições para acionar |
actions | array | Nao | Ações legadas. Complementares: não substituem o flow. |
schedule_config | object | Nao | Configuração de agendamento (quando trigger_type = scheduled) |
flow | object | Sim | Editor visual: { nodes, connections, viewport }. nodes precisa ser não vazio — é o único formato que o motor executa. |
Sem fluxo, sem automação
O motor só executa automações baseadas em fluxo. Criar uma sem flow.nodes preenchido retorna 422 — antes o registro era salvo e aparecia ativo na lista, mas nenhum evento o disparava.
O formato legado (trigger + actions sem flow) não é mais aceito na criação. Monte o fluxo visual — POST .../automations/validate_flow valida o grafo antes de salvar.
{
"error": "An automation without a flow is never executed by the engine. Build the flow before saving.",
"valid": false,
"errors": [
"An automation without a flow is never executed by the engine. Build the flow before saving."
]
}curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/automations" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pipeline_automation": {
"name": "Notificar em Negociacao",
"active": true,
"trigger_type": "event",
"pipeline_id": 1,
"flow": {
"nodes": [
{
"id": "trigger-1",
"type": "trigger",
"position": { "x": 0, "y": 0 },
"data": {
"type": "item_moved",
"event_type": "pipeline_item_moved",
"pipeline_id": "1",
"from_stage_id": "lead",
"to_stage_id": "negociacao",
"conditions": [
{ "field": "conversation.label_list", "operator": "equals", "value": "vip" }
]
}
},
{
"id": "action-1",
"type": "action",
"position": { "x": 360, "y": 0 },
"data": { "type": "send_message", "content": "O card entrou em negociacao" }
}
],
"connections": [
{ "id": "edge-1", "source": "trigger-1", "target": "action-1" }
],
"viewport": { "x": 0, "y": 0, "zoom": 1 }
}
}
}'/api/v1/accounts/{account_id}/pipeline/automations/{id}Atualiza uma automação.
Duas edições recusadas com 422
A mesma regra do POST vale aqui, mas avaliada sobre o estado resultante — o PATCH só é recusado quando é ele que deixa a automação ativa e inexecutável:
- Esvaziar o fluxo (
flow.nodesvazio) de uma automação ativa. - Ativar (
active: true) uma automação que não tem fluxo — tipicamente uma legada, anterior a esta regra.
Automações legadas que já estão ativas continuam editáveis e, principalmente, continuam podendo ser desativadas: o objetivo é tirar a conta desse estado, não prendê-la nele. Enviar um flow com nós inválidos retorna 422 pelo validador de fluxo, com a lista de erros em errors.
/api/v1/accounts/{account_id}/pipeline/automations/{id}Remove uma automação.
/api/v1/accounts/{account_id}/pipeline/automations/{id}/duplicateClona uma automação existente.
/api/v1/accounts/{account_id}/pipeline/automations/{id}/exportExporta a automação como JSON, no formato aceito por POST /pipeline/automations/import. Restrito a administradores; a exportação fica registrada no log de auditoria.
{
"version": "1.0",
"exported_at": "2026-09-27T12:00:00Z",
"automation": {
"name": "Notificar ao entrar em Negociação",
"description": "Avisa o responsável quando um card chega na negociação",
"trigger": { "event": "stage_change", "to_stage": "1_negociacao" },
"conditions": [],
"actions": [],
"flow": { "nodes": [], "connections": [], "viewport": { "x": 0, "y": 0, "zoom": 1 } }
}
}O arquivo exportado não é mascarado
actions e flow saem exatamente como estão salvos — inclusive credenciais configuradas em nós (como cabeçalhos de autenticação de requisições HTTP). Trate o arquivo como segredo.
Contrato canônico do fluxo visual
O objeto flow usa nodes, connections e, opcionalmente, viewport. Cada node possui id, type, dados em data e pode incluir position. O objeto node.data é extensível: o backend preserva chaves e tipos JSON necessários para cada ação, remove tags HTML de strings e valida a estrutura executável. Por isso, a API não define um schema fechado de parâmetros para todas as ações.
Use nomes canônicos em novas integrações
Create, update e import normalizam o fluxo antes de gravar. IDs numéricos aceitos nos filtros são persistidos como strings; arrays de IDs aceitam apenas inteiros positivos. Clientes novos devem enviar somente as chaves canônicas abaixo.
Filtros canônicos de trigger em node.data
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
event_type | string | Nao | Evento despachado; para movimento de card use pipeline_item_moved |
pipeline_id | string | Nao | ID positivo do pipeline, normalizado para string |
from_stage_id / to_stage_id | string | Nao | Chaves dos estágios de origem e destino |
pattern | string | Nao | Padrão textual do gatilho |
inbox_ids / added_label_ids | string[] | Nao | Arrays de IDs positivos, sem duplicatas após normalização |
stage_ids | string[] | Nao | Lista de chaves de estágio |
channel_type | string | Nao | Tipo canônico do canal |
message_type | string | Nao | all | incoming | outgoing |
only_first_message | boolean | Nao | Aceita somente boolean real, não "true"/"false" em string |
from_status / to_status | string | Nao | Status anterior e novo |
label_action | string | Nao | any | added | removed |
filter_label_id | string | Nao | ID positivo da label, normalizado para string |
added_labels / removed_labels | string[] | Nao | Nomes de labels; cada item deve ser string não vazia |
Campos canônicos em condições
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
contact.phone_number | string | Nao | Telefone do contato |
conversation.assignee_id | string | Nao | ID do responsável da conversa |
conversation.team_id | string | Nao | ID do time da conversa |
inbox.channel_type | string | Nao | Classe do canal da inbox |
conversation.label_list | string[] | Nao | Lista real de labels da conversa; equals/not_equals com valor escalar testa pertinência sem diferenciar maiúsculas |
Aliases legados existem apenas para compatibilidade
O backend ainda lê e normaliza os aliases pipeline_card_stage_changed, eventType, pipelineId, funnel_id, fromColumn, stage_from, toColumn, stage_to, keyword, inboxIds, channelType, messageType, onlyFirstMessage, stageIds, fromStatus, toStatus, labelAction, filterLabelId, addedLabelIds, addedLabels e removedLabels. Condições antigas com contact.phone, conversation.assignee, conversation.team, conversation.channel ou conversation.label também são convertidas. Eles não devem ser gravados por clientes novos.
Aliases equivalentes com o mesmo valor são consolidados. Valores conflitantes ou tipos inválidos retornam 422; create, update e import usam a mesma fronteira de normalização canônica.
/api/v1/accounts/{account_id}/pipeline/automations/validate_flowValida um flow sem salvar. Envie { flow: { nodes, connections, viewport } }.
{ "valid": true, "errors": [], "warnings": [] }{
"valid": false,
"errors": ["Node trigger-1: conflicting values for pipeline_id, pipelineId"],
"warnings": []
}/api/v1/accounts/{account_id}/pipeline/automations/importImporta uma automação JSON, normaliza o flow e sempre cria o registro inativo.
Resposta de erro do import
Para o mesmo conflito do exemplo acima, o import retorna exatamente { "error": "Flow Node trigger-1: conflicting values for pipeline_id, pipelineId" } com status 422.
Executar e Testar
Quem pode executar e simular
execute dispara de verdade e exige administrador; dry_run não produz efeito e aceita administrador ou agente com pipeline_manage. Ambos aceitam, no nível raiz, conversation_id (ID interno da conversa, não o display ID) oucontact_id. Se os dois forem enviados, a conversa tem precedência.
/api/v1/accounts/{account_id}/pipeline/automations/{id}/executeExecuta manualmente uma automação.
Body (opcional)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
conversation_id | integer | Nao | ID interno da conversa usada como contexto |
contact_id | integer | Nao | ID do contato usado como contexto quando conversation_id não é enviado |
/api/v1/accounts/{account_id}/pipeline/automations/{id}/dry_runExecuta simulação sem efeito real (teste).
Body (opcional)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
conversation_id | integer | Nao | ID interno da conversa usada como contexto |
contact_id | integer | Nao | ID do contato usado como contexto quando conversation_id não é enviado |
Dry Run
Permite testar sem executar ações reais. Sucesso retorna 200 com success, dry_run, dados da automação, steps, log e summary. Erro de validação/simulação retorna o objeto direto com success: false e status 422.
/api/v1/accounts/{account_id}/pipeline/automations/{id}/validateValida a configuração da automação.
/api/v1/accounts/{account_id}/pipeline/automations/{id}/executionsHistórico de execuções da automação.
/api/v1/accounts/{account_id}/pipeline/automations/all_executionsHistórico de execuções de todas as automações visíveis ao usuário, da mais recente para a mais antiga. Cada execução traz também { automation: { id, name } }.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
status(query) | string | Nao | pending, running, completed, failed ou cancelled. Outro valor (ou all) não filtra. |
limit(query) | integer | Nao | Padrão 50, entre 1 e 200 |
offset(query) | integer | Nao | Deslocamento (padrão 0) |
{
"executions": [
{
"id": 901,
"status": "failed",
"trigger_event": "stage_change",
"started_at": "2026-09-27T11:59:58Z",
"completed_at": "2026-09-27T11:59:59Z",
"execution_time_ms": 842,
"nodes_executed": 3,
"error_message": "...",
"failed_node_id": "node_3",
"failed_node_error": "...",
"conversation_id": null,
"contact_id": 10,
"pipeline_item_id": null,
"pipeline_card_id": 5,
"logs": [],
"created_at": "2026-09-27T11:59:58Z",
"automation": { "id": 1, "name": "Notificar ao entrar em Negociação" }
}
],
"meta": { "total": 1, "limit": 50, "offset": 0 }
}Estatísticas de Automação
/api/v1/accounts/{account_id}/pipeline/automations/{id}/statsMétricas de uma automação específica.
/api/v1/accounts/{account_id}/pipeline/automations/dashboardDashboard de todas as automações.
/api/v1/accounts/{account_id}/pipeline/automations/{id}/audit_logsLogs de auditoria da automação.
/api/v1/accounts/{account_id}/pipeline/automations/all_audit_logsLogs de auditoria de todas as automações visíveis ao usuário, do mais recente para o mais antigo. Cada registro traz também { automation: { id, name, active } }.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
audit_action(query) | string | Nao | create, update, delete, activate, deactivate, execute, duplicate, import, export ou rotate_webhook_token |
user_id(query) | integer | Nao | Somente ações desse usuário |
since(query) | string | Nao | Data inicial YYYY-MM-DD (início do dia no fuso da conta) |
until(query) | string | Nao | Data final YYYY-MM-DD (fim do dia no fuso da conta) |
limit(query) | integer | Nao | Padrão 50, entre 1 e 200 |
offset(query) | integer | Nao | Deslocamento (padrão 0) |
{
"audit_logs": [
{
"id": 44,
"action": "update",
"resource_name": "Notificar ao entrar em Negociação",
"description": "...",
"changes": {},
"user": { "id": 3, "name": "Administrador", "email": "admin@exemplo.com" },
"created_at": "2026-09-27T10:00:00Z",
"automation": { "id": 1, "name": "Notificar ao entrar em Negociação", "active": true }
}
],
"meta": { "total": 1, "limit": 50, "offset": 0 }
}O filtro de ação é audit_action
O parâmetro se chama audit_action; um ?action= é ignorado. Data fora do formato YYYY-MM-DD, ou since depois de until, retorna 422.
/api/v1/accounts/{account_id}/pipeline/automations/{id}/rate_limitConsumo atual do limite de execuções da automação e da conta na janela vigente. Somente leitura; exige administrador ou pipeline_manage.
{
"automation_id": 1,
"automation_name": "Notificar ao entrar em Negociação",
"rate_limit": {
"enabled": true,
"automation": { "limit": 100, "used": 12, "remaining": 88 },
"account": { "limit": 1000, "used": 140, "remaining": 860 },
"window_seconds": 3600,
"window_minutes": 60
},
"within_limit": true
}/api/v1/accounts/{account_id}/pipeline/automations/rate_limitsConsumo do limite da conta e de cada automação ativa visível ao usuário. Somente leitura; exige administrador ou pipeline_manage.
{
"account": { "limit": 1000, "used": 140, "remaining": 860, "window_minutes": 60 },
"automations": [
{ "id": 1, "name": "Notificar ao entrar em Negociação", "limit": 100, "used": 12, "remaining": 88, "within_limit": true }
]
}Gatilho por Webhook
Uma automação com gatilho de webhook recebe uma URL própria, com um token secreto no caminho. Qualquer sistema externo que faça um POST nessa URL dispara a automação. A URL não aparece na representação comum da automação (que traz apenas webhook_configured); ela é lida e rotacionada pelos dois endpoints abaixo, restritos a administradores. As respostas vêm com Cache-Control: no-store.
/api/v1/accounts/{account_id}/pipeline/automations/{id}/webhook_credentialsRetorna a URL de disparo da automação.
{
"webhook_configured": true,
"webhook_url": "https://chat.seudominio.com/api/v1/pipeline_automation_webhooks/<token>"
}/api/v1/accounts/{account_id}/pipeline/automations/{id}/rotate_webhook_tokenGera um novo token e devolve a nova URL. A URL anterior deixa de funcionar imediatamente. A rotação fica registrada no log de auditoria.
{ "webhook_configured": true, "webhook_url": "https://chat.seudominio.com/api/v1/pipeline_automation_webhooks/<novo_token>" }Endpoint público de disparo
Sem api_access_token — o token da URL é a credencial
Este endpoint não fica sob /api/v1/accounts/{account_id} e não usa api_access_token: quem tem a URL pode disparar a automação. Guarde a URL como segredo e rotacione-a se ela vazar.
/api/v1/pipeline_automation_webhooks/{token}Dispara a automação dona do token com o corpo JSON enviado, que fica disponível para o fluxo.
Requisição
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
token(path) | string | Sim | Token da URL obtida em webhook_credentials |
Content-Type(header) | string | Sim | Precisa ser application/json; outro tipo retorna 415 |
(corpo)(body) | object | Nao | Objeto JSON de até 1 MB. Corpo vazio é aceito. |
curl -X POST "https://chat.seudominio.com/api/v1/pipeline_automation_webhooks/<token>" \
-H "Content-Type: application/json" \
-d '{ "pedido": "1234", "valor": 490 }'{ "accepted": true, "triggered_at": "2026-09-27T12:00:00Z" }Resposta síncrona com o nó de resposta HTTP
Se o fluxo tem um nó de resposta HTTP, é curto (até 10 nós) e não tem espera, agendamento nem nós que chamam serviços externos, ele roda na própria requisição e a resposta é a definida nesse nó (status, corpo e cabeçalhos). Nos demais casos a resposta é o 202 acima.
/api/v1/pipeline_automation_webhooksForma legada: o token vai no corpo JSON, no campo token, em vez do caminho. O campo é removido do payload antes de chegar ao fluxo. Mesmas regras e respostas da forma com token no caminho; prefira a URL devolvida por webhook_credentials.
/api/v1/pipeline_automation_webhooks/{token}/verifyConfere se a URL está pronta para receber disparos, sem executar nada. Sem corpo de resposta.
Templates de Automação
Modelos prontos de automação, comuns a todas as contas e somente leitura. Qualquer usuário autenticado pode consultá-los; criar uma automação a partir de um modelo exige administrador ou pipeline_manage.
/api/v1/accounts/{account_id}/pipeline/automation_templatesLista os templates, os destacados primeiro e depois por nome.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
category(query) | string | Nao | general, sales, support, marketing, onboarding, notifications ou integrations |
locale(query) | string | Nao | Idioma dos templates (padrão pt-BR) |
featured(query) | string | Nao | true para trazer só os destacados |
order(query) | string | Nao | popular para ordenar por número de usos |
{
"templates": [
{
"id": 7,
"name": "Boas-vindas ao novo lead",
"description": "...",
"category": "sales",
"category_label": "Vendas",
"icon": "...",
"is_system": true,
"is_featured": true,
"usage_count": 42
}
],
"categories": ["general", "sales", "support", "marketing", "onboarding", "notifications", "integrations"]
}/api/v1/accounts/{account_id}/pipeline/automation_templates/categoriesLista as categorias com rótulo e quantidade de templates.
{ "categories": [ { "id": "sales", "label": "Vendas", "count": 5 } ] }/api/v1/accounts/{account_id}/pipeline/automation_templates/{id}Detalhe de um template: os mesmos campos da listagem mais flow, trigger, conditions e actions.
/api/v1/accounts/{account_id}/pipeline/automation_templates/{id}/useCria na conta uma automação a partir do template. A automação nasce inativa; revise e ative com PATCH /pipeline/automations/{id}.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
name | string | Nao | Nome da nova automação. Omitido, usa o nome do template. |
Catálogo de Produtos
O que a conta vende. Cadastrado aqui, o produto fica selecionável ao fechar um card como ganho — e é esse vínculo que permite medir receita por produto. Ler o catálogo acompanha o acesso ao Pipeline; criar, editar e desativar exigem permissão de gestão.
Depende do módulo Oportunidades
O catálogo de produtos faz parte de Oportunidades, ativado por conta. Sem ele, estes endpoints respondem 403 com code: pipeline_opportunities_disabled — e o funil continua marcando ganho e perdido normalmente, com o valor no próprio card. O razão de vendas (histórico de ciclos) não depende do módulo: toda conta acumula histórico ao fechar um card e pode consultá-lo.
/api/v1/accounts/{account_id}/pipeline/productsLista o catálogo, ordenado por posição e nome.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
active_only | boolean | Nao | Traz somente os produtos ativos |
pipeline_id | integer | Nao | Somente os disponíveis naquele funil (inclui os irrestritos) |
per_page | integer | Nao | Padrão 50, máximo 200 |
offset | integer | Nao | Paginação |
pipeline_ids vazio significa TODOS os funis
O produto sem restrição vale em qualquer funil. O campo pipeline_ids_malformed: truesinaliza uma restrição corrompida: nesse caso o servidor trata o produto como indisponível em todo lugar (fail-closed), e você não deve ler a lista vazia como "todos os funis".
/api/v1/accounts/{account_id}/pipeline/products/{id}Retorna um produto do catálogo, envolto em { product }, com os mesmos campos de cada item da listagem. ID inexistente ou de outra conta retorna 404 com { error: 'Product not found' }.
/api/v1/accounts/{account_id}/pipeline/productsCria um produto no catálogo.
Body (pipeline_product)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
name | string | Sim | Nome do produto ou serviço |
sku | string | Nao | Código único na conta. Duplicado retorna 422. |
description | string | Nao | Descrição livre |
category | string | Nao | Categoria para agrupar no catálogo |
default_value | number | Nao | Preço de tabela. Serve de ponto de partida ao fechar — o valor cobrado pode ser outro. |
currency | string | Nao | ISO-4217. Omitido, herda a moeda da conta (não o padrão BRL da coluna). |
active | boolean | Nao | Padrão true |
position | integer | Nao | Ordem no catálogo |
pipeline_ids | array | Nao | Restringe a esses funis. Vazio ou ausente = disponível em todos. |
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/products" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "pipeline_product": { "name": "Consultoria de Implantacao", "sku": "CONS-IMPL", "default_value": 3000 } }'pipeline_ids precisa ser um array de ids
Qualquer outro formato retorna 422 em vez de ser descartado em silêncio: uma restrição malformada viraria um produto disponível em todos os funis — o oposto da intenção.
/api/v1/accounts/{account_id}/pipeline/products/{id}Atualiza um produto. Mesmos campos do POST.
/api/v1/accounts/{account_id}/pipeline/products/{id}Desativa o produto (active: false). Não apaga.
Produto não é apagado, é desativado
O histórico financeiro aponta para ele, e o banco recusa o DELETE justamente para não deixar receita órfã. Desativado, o produto some da seleção de venda mas continua nomeando as vendas passadas. Para reativar, use PATCH com active: true.
/api/v1/accounts/{account_id}/pipeline/products/performanceQuanto cada produto vendeu num período.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
won_start | string (YYYY-MM-DD) | Nao | Primeiro dia do intervalo. Omitido = desde sempre. |
won_end | string (YYYY-MM-DD) | Nao | Último dia, incluído por inteiro. Anterior a won_start retorna 422. |
pipeline_id | integer | Nao | Restringe a medição a um funil |
curl "https://chat.seudominio.com/api/v1/accounts/1/pipeline/products/performance?won_start=2026-07-01&won_end=2026-07-31" \
-H "api_access_token: YOUR_TOKEN"Como ler a resposta
Todos os produtos voltam, inclusive os que não venderam (com performance: []) — um produto que não vende é o dado mais acionável da lista. performance traz uma entrada por moeda: somar BRL com USD produziria um número que não existe. Vendas estornadas ficam de fora.
O bloco unlinked, à parte, conta as vendas sem produto vinculado — as anteriores ao catálogo e as fechadas com valor livre. Sem ele a lista mostraria zeros sem explicação.
O intervalo é resolvido no fuso da conta e meta.time_zone informa qual fuso foi aplicado. Datas com hora (ou qualquer formato fora de YYYY-MM-DD) retornam 422: elas carregariam um deslocamento próprio e mediriam um recorte diferente do pedido.
Agentes veem apenas os próprios funis
Para um agente, a soma cobre somente os funis de que ele participa — inclusive no bloco unlinked. Administradores recebem a conta inteira.
Razão de Vendas do Card
Cada venda de um card é um lançamento. O razão é append-only: correção se faz por estorno, nunca apagando a linha — e por isso o histórico consegue distinguir uma correção de um sumiço.
Depende do módulo Oportunidades
Consultar o histórico (GET .../opportunities) e estornar um lançamento (POST .../opportunities/{id}/void) não dependem do módulo: toda conta acumula ciclos ao fechar um card, e como o registro é imutável, o estorno é o único caminho de correção.
Já registrar uma venda avulsa (POST .../opportunities, sem mover o card) faz parte de Oportunidades: sem o módulo ativo, responde 403 com code: pipeline_opportunities_disabled. Nesse caso pipeline_product_id também é ignorado no mark_won.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/opportunitiesLista as vendas do card em ordem cronológica, com o resumo do acumulado.
/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/opportunitiesRegistra uma venda avulsa no card (o caminho normal é fechar o card como ganho).
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
total_value | number | Nao | Valor total. Tem precedência sobre quantity x unit_value. |
quantity | number | Nao | Quantidade vendida (padrão 1) |
unit_value | number | Nao | Valor unitário |
pipeline_product_id | integer | Nao | Produto vendido. Id de outra conta retorna 404. |
title | string | Nao | Descrição do lançamento. Omitido, herda o nome do produto e depois o título do card. |
note | string | Nao | Observação |
items | array | Nao | Vender vários produtos na mesma venda (1 a 50 itens). Mutuamente exclusivo com total_value/quantity/unit_value/pipeline_product_id acima. |
Venda avulsa acontece AGORA
O lançamento é datado no momento da chamada, não no fechamento do card. Herdar a data do ganho retrodataria receita nova para um período já fechado.
items segue o mesmo formato do mark_won
Cada item é um objeto { pipeline_product_id, quantity, unit_value, total_value, title, note, custom_attributes }. Mesmas regras: quantity padrão 1, total_value obrigatório sem unit_value e deve bater com quantity * unit_value quando os dois vêm juntos. A resposta traz o array items completo, e os campos soltos pipeline_product_id/unit_value no nível raiz do opportunitysó vêm preenchidos com exatamente um item — veja o aviso na seção "Status do Deal" acima, que vale igual aqui.
/api/v1/accounts/{account_id}/pipeline/opportunities/{id}/voidEstorna uma venda: ela deixa de contar na receita, mas continua visível no histórico.
Body
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
reason | string | Sim | Motivo do estorno. Obrigatório, de 1 a 255 caracteres. |
Estorno não apaga
A linha permanece, marcada, com autor, data e motivo — e o acumulado do card é recalculado. Estornar duas vezes retorna 409.
Quem pode estornar
Somente administrador ou agente cuja função personalizada tenha a permissão pipeline_manage. Qualquer outro agente recebe 403.
reason é obrigatório
POST .../void sem reason, com reason vazio ou com mais de 255 caracteres retorna 422. Se a sua integração ainda não envia esse campo, atualize-a antes de estornar — não há fallback silencioso.
/api/v1/accounts/{account_id}/pipeline/opportunities/reportRelatório de receita da conta: quanto entrou, por funil, por vendedor, por origem e por dia, a partir das vendas registradas.
Query
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
start_date(query) | string | Nao | Data inicial YYYY-MM-DD, pela data da venda (won_at), no fuso da conta |
end_date(query) | string | Nao | Data final YYYY-MM-DD, inclusiva (até o fim do dia) |
pipeline_id(query) | integer | Nao | Restringe a um funil. Funil inexistente, ou fora do alcance do agente, retorna 404. |
{
"period": { "start_date": "2026-09-01", "end_date": "2026-09-27", "time_zone": "America/Sao_Paulo" },
"totals_by_currency": [
{ "currency": "BRL", "revenue": "48000.0", "sales_count": 12, "average_ticket": "4000.0", "items_sold": "15.0" }
],
"voided_by_currency": [],
"by_pipeline": [ { "pipeline_id": 1, "pipeline_name": "Vendas B2B", "currency": "BRL", "revenue": "48000.0", "sales_count": 12 } ],
"by_seller": [ { "user_id": 3, "user_name": "Vendedor", "currency": "BRL", "revenue": "30000.0", "sales_count": 7 } ],
"by_source": [ { "source": "manual", "sales_count": 12 } ],
"timeline": [ { "date": "2026-09-02", "currency": "BRL", "sales_count": 2, "revenue": "8000.0" } ],
"repeat": { "first_cycle_sales": 10, "repeat_sales": 2 }
}Como ler o relatório
- Toda soma é por moeda — valores em moedas diferentes nunca são somados entre si.
- Venda sem funil (funil apagado ou venda sem vínculo) aparece em
by_pipelinecompipeline_idnulo; venda sem vendedor identificado aparece emby_sellercomuser_idnulo — os detalhamentos fecham com o total. - Valores monetários (
revenue,average_ticket) eitems_soldchegam como string decimal; converta antes de somar.by_sourceconta vendas por origem do registro (manual,api,automationoubackfill). repeatsepara a primeira venda de cada card das recompras no mesmo card.- Administrador vê a conta inteira; agente vê só a receita dos funis de que participa.
- Sem datas, o período é aberto. Data fora de
YYYY-MM-DD,start_datedepois deend_dateoupipeline_idnão numérico retornam422. - Faz parte do módulo Oportunidades: com ele desativado na conta, a resposta é
403comcode: pipeline_opportunities_disabled.
Webhooks do Pipeline
Receba eventos do Pipeline em tempo real numa URL externa. Gerenciar webhooks requer permissão de administrador.
A URL precisa ser pública
A URL do webhook deve resolver para um IP público. Endereços privados/internos (127.0.0.0/8, 10/8, 172.16/12, 192.168/16, 169.254/16, IPv6 ULA/link-local) são bloqueados por proteção anti-SSRF e a criação retorna 422 — a verificação também roda no momento do envio.
/api/v1/accounts/{account_id}/pipeline/webhooksCria um webhook. Body envolto em pipeline_webhook.
Body (pipeline_webhook)
| Nome | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
name | string | Sim | Nome do webhook |
url | string | Sim | URL HTTP ou HTTPS pública que receberá os POSTs. Em produção, prefira HTTPS. |
pipeline_id | integer | Nao | Restringe o webhook a um pipeline da conta. Omitido/null cria um webhook global. |
active | boolean | Nao | Ativa a entrega; padrão true |
events | string[] | Sim | Lista não vazia de eventos disponíveis (ver abaixo). Evento inválido retorna 422. |
curl -X POST "https://chat.seudominio.com/api/v1/accounts/1/pipeline/webhooks" \
-H "api_access_token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pipeline_webhook": {
"name": "Integracao CRM",
"url": "https://example.com/hooks/pipeline",
"events": ["pipeline_card_won", "pipeline_card_lost"]
}
}'{ "id": 71, "name": "Integracao CRM", "url": "https://example.com/hooks/pipeline", "pipeline_id": null, "pipeline_name": null, "events": ["pipeline_card_won", "pipeline_card_lost"], "active": true, "secret": "<64 hex>" }/api/v1/accounts/{account_id}/pipeline/webhooksLista os webhooks (admin).
/api/v1/accounts/{account_id}/pipeline/webhooks/{id}Detalhes de um webhook.
/api/v1/accounts/{account_id}/pipeline/webhooks/{id}Atualiza name, url, pipeline_id, events ou active.
/api/v1/accounts/{account_id}/pipeline/webhooks/{id}Remove o webhook.
/api/v1/accounts/{account_id}/pipeline/webhooks/{id}/testDispara um POST de teste síncrono para a URL configurada.
/api/v1/accounts/{account_id}/pipeline/webhooks/{id}/regenerate_secretGera um novo secret HMAC (invalida o anterior).
Formatos de resposta
A listagem retorna { payload: [...] }. Criação (201), detalhe e atualização (200) retornam o objeto diretamente, incluindopipeline_id, pipeline_name, active, secret e os dados da última entrega. O DELETE retorna 204 No Content.
Eventos disponíveis
Oito eventos:
pipeline_card_created— card criadopipeline_card_updated— card atualizadopipeline_card_deleted— card removido (soft delete)pipeline_card_stage_changed— mudou de estágio (incluifrom_stage/to_stage)pipeline_card_won— marcado como ganho;won_atfica emdata.cardepre_won_stageemdata.card.item_detailspipeline_card_lost— marcado como perdido;lost_reasonfica emdata.cardepre_lost_stageemdata.card.item_detailspipeline_card_owner_changed— responsável alteradopipeline_card_sla_exceeded— o card passou do prazo na etapa. Único evento que nasce de um relógio, não de uma ação: é justamente o que acontece sem ninguém olhando. Além do card, o payload trazsla_hours(o prazo da etapa),seconds_in_stage(o tempo já gasto) estage— só "estourou" não distingue um minuto de atraso de uma semana parada.
O SLA é verificado de hora em hora, e avisa uma vez por etapa
A varredura roda a cada hora, então o evento chega depois do estouro, não no instante dele. E ele é enviado uma vez por etapa: mover o card e trazê-lo de volta rearma o aviso, ficar parado não repete. Configure o prazo em stages[].sla_hours do funil e habilite stage_sla_enabled.
Formato do payload
{
"event": "pipeline_card_won",
"timestamp": "2026-06-03T23:21:38Z",
"data": {
"card": {
"id": 12473,
"pipeline_id": 8143,
"pipeline_stage": "8143_ganho",
"won_at": "...",
"contact_id": 21,
"item_details": { "pre_won_stage": "8143_negociacao" }
}
}
}Verificação da assinatura (HMAC)
Cada POST inclui o header X-Pipeline-Webhook-Signature: sha256=<hex>, onde <hex> é o HMAC-SHA256 do corpo JSON cru usando o secret do webhook (64 hex). Recalcule e compare para garantir a autenticidade:
import crypto from "crypto";
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-pipeline-webhook-signature"]));