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_assign e bulk_actions/delete exige 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_run e consultar rate limit exige administrador ou agente com a permissão pipeline_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 (marca discarded_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) e reorder filtram os IDs recebidos pela sua conta antes de agir: qualquer ID de outra conta é silenciosamente ignorado (no-op), nunca apagado nem movido. O reorder ainda exige um pipeline_id de escopo para reforçar o isolamento entre pipelines: use o campo raiz canônico; por compatibilidade, o backend aceita o pipeline_id da primeira posição como fallback legado.

Pipelines

GET/api/v1/accounts/{account_id}/pipelines

Lista os pipelines visíveis ao usuário autenticado; administradores veem todos os pipelines da conta.

bash
curl -s "https://chat.seudominio.com/api/v1/accounts/1/pipelines" \
  -H "api_access_token: YOUR_TOKEN" | jq .
200Array de pipelines (sem envelope). stages é um objeto/hash com a chave do estágio como key.
json
[
  {
    "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"
  }
]
GET/api/v1/accounts/{account_id}/pipelines/{id}

Retorna um pipeline pelo ID, sem envelope, no mesmo formato de cada item da listagem.

Path

NomeTipoObrigatorioDescricao
id(path)integerSimID 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.

GET/api/v1/accounts/{account_id}/pipelines/{pipeline_id}/pipeline_cards

Lista 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

NomeTipoObrigatorioDescricao
pipeline_id(path)integerSimID do pipeline cujos cards serão listados
bash
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 .
POST/api/v1/accounts/{account_id}/pipelines

Cria 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

NomeTipoObrigatorioDescricao
pipeline.namestringSimNome do pipeline
pipeline.descriptionstringNaoDescrição
pipeline.activebooleanNaoStatus de ativação
pipeline.inbox_idintegerNaoInbox associada ao pipeline
pipeline.stage_sla_enabledbooleanNaoAtiva o SLA por estágio
pipeline.default_for_appointmentsbooleanNaoMarca 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.agentsarrayNaoAgentes membros do pipeline; o backend incorpora a lista em settings.agents
pipeline.settingsobjectNaoConfigurações allowlisted do pipeline (max 64 KB)
pipeline.stagesobjectSimObjeto 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}.namestringSimNome do estágio
pipeline.stages.{key}.colorstringNaoCor em hex (ex: "#3B82F6")
pipeline.stages.{key}.iconstringNaoÍcone do estágio
pipeline.stages.{key}.positionintegerNaoOrdem crescente de exibição; o menor valor aparece primeiro (a interface normalmente inicia em 0)
pipeline.stages.{key}.descriptionstringNaoDescrição do estágio
pipeline.stages.{key}.is_entry_stagebooleanNaoMarca o estágio de entrada
pipeline.stages.{key}.is_won_stagebooleanNaoMarca o estágio terminal de ganho
pipeline.stages.{key}.is_lost_stagebooleanNaoMarca o estágio terminal de perda
pipeline.stages.{key}.automation_actionsJSONNaoAções de automação associadas ao estágio
pipeline.stages.{key}.wip_limitintegerNaoLimite de cards em andamento
pipeline.stages.{key}.sla_hoursnumberNaoPrazo de SLA do estágio, em horas
pipeline.stages.{key}.message_templatesarrayNaoTemplates 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.

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

NomeTipoObrigatorioDescricao
pipeline.namestringNaoNome do pipeline
pipeline.descriptionstringNaoDescrição
pipeline.activebooleanNaoStatus de ativação
pipeline.inbox_idintegerNaoInbox associada ao pipeline
pipeline.stage_sla_enabledbooleanNaoAtiva o SLA por estágio
pipeline.default_for_appointmentsbooleanNaoMarca 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.agentsarrayNaoAgentes membros; mesclados em settings.agents
pipeline.stagesobjectNaoHash com stages keyed por ID real (ex: "1_qualificado"). Cada estágio aceita os mesmos campos da tabela de criação.
pipeline.settingsobjectNaoConfiguraçõ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.

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

GET/api/v1/accounts/{account_id}/pipelines/{pipeline_id}/stages

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

200Estágios ordenados por position
json
{
  "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.

GET/api/v1/accounts/{account_id}/pipeline_cards

Lista 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)

NomeTipoObrigatorioDescricao
pipeline_id(query)integerNaoFiltrar por pipeline (todos os cards desse funnel)
pipeline_stage(query)stringNaoFiltrar por um estágio específico (ex: "1_lead")
conversation_display_id(query)integerNaoFiltrar por conversa associada (display_id, não ID global)
contact_id(query)integerNaoFiltrar por contato (v4.12.3.0+)
search(query)stringNaoBusca textual server-side (max 200 caracteres) por card, contato, responsável, inbox, identificadores ou estágio.
labels[](query)string[]NaoTítulos de labels da conversa vinculada ao card. Match OR (qualquer um dos títulos).
priority[](query)string[]NaoPrioridade do card: none, low, medium, high, urgent. Match OR.
value_min(query)numberNaoValor 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)numberNaoValor máximo do card (<=). Cards sem valor algum são excluídos (filtrar por valor implica ter valor).
agent_id(query)integerNaoID do responsável (owner) do card. Use -1 ou "unassigned" para cards sem responsável.
date_start(query)stringNaoData inicial de criação (created_at), formato YYYY-MM-DD, interpretada no fuso da conta.
date_end(query)stringNaoData final de criação (created_at), formato YYYY-MM-DD, fuso da conta.
status(query)stringNaoEstado do deal: open, won, lost ou closed.
sla_exceeded(query)booleanNaotrue = apenas cards abertos com SLA vencido (sla_overdue).
stages[](query)string[]NaoLista de estágios (pipeline_stage). Match OR — cards em qualquer um dos estágios informados.
limit(query)integerNaoItens por página (default 50). O valor é limitado ao intervalo de 1 a 500.
cursor(query)stringNaoCursor opaco retornado em meta.next_cursor. Use para buscar a próxima página sem recalcular offset.
offset(query)integerNaoOffset 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)

NomeTipoObrigatorioDescricao
pipeline_id(query)integerNaoFiltrar por pipeline
pipeline_stage(query)stringNaoFiltrar por estágio (ex: "69_lead")
owner_id(query)integerNaoFiltrar por responsável (além de agent_id da tabela acima)
contact_id(query)integerNaoFiltrar por contato (v4.12.3.0+)
lead_score_category(query)stringNaohot, warm, cold (exclusivo da canônica)
page(query)integerNaoPágina atual(default: 1)
per_page(query)integerNaoItens 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.

bash
# 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 .
200Lista de cards (legacy /pipeline_cards)
json
{
  "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 listagemlimit (padrão 50, máx. 500) + offset ou cursorpage (padrão 1) + per_page (padrão 25, máx. 100)
meta da listagemlimit, offset, has_more, next_cursor, stage_countscurrent_page, per_page, total_pages, total_count
Card na listagemFormato enxuto do quadro (inclui active_sequence, campos de SLA)Mesmo objeto do detalhe (GET /pipeline/cards/{id})
Filtros exclusivosconversation_display_id, include_linked_conversations, exclude_idowner_id, lead_score_category
Status de criação200201
Criação em pipeline fora do alcance404 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 PATCH422 apontando para deal_statusnão está na lista de campos aceitos — ignorado
Motivo no DELETEnão aceitareason (ou discard_reason), gravado na lixeira
Sem permissãoRecusa da policy: 401 para usuário; 403 com reason: action_not_allowed_for_bot e allowed_actions para Agent BotRecusa 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.

GET/api/v1/accounts/{account_id}/pipeline/cards

Lista 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

NomeTipoObrigatorioDescricao
page(query)integerNaoPágina (padrão 1)
per_page(query)integerNaoItens por página (padrão 25, máximo 100)
pipeline_id(query)integerNaoFiltra por pipeline
pipeline_stage(query)stringNaoFiltra por estágio ({pipeline_id}_{slug})
owner_id(query)integerNaoFiltra por responsável
contact_id(query)integerNaoFiltra pelo contato primário do card
lead_score_category(query)stringNaohot, warm ou cold
200Envelope { data, meta }. Cada item é o objeto completo do card, acrescido dos campos de estatística de atividades.
json
{
  "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 }
}
POST/api/v1/accounts/{account_id}/pipeline/cards

Cria 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 }.

bash
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 } }'
GET/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.

PATCH/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.

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

NomeTipoObrigatorioDescricao
reasonstringNaoMotivo 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.

GET/api/v1/accounts/{account_id}/pipeline/cards/discarded

Lista os cards descartados, do mais recente para o mais antigo.

Query

NomeTipoObrigatorioDescricao
pipeline_id(query)integerNaoRestringe à lixeira desse pipeline. Pipeline inexistente ou de outra conta retorna 404. Sem ele, lista a lixeira da conta inteira.
page(query)integerNaoPágina (padrão 1)
per_page(query)integerNaoItens por página (padrão 25, máximo 100)
200Envelope { data, meta } no mesmo formato de GET /pipeline/cards; cada card traz também os dados do descarte.
json
{
  "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.

POST/api/v1/accounts/{account_id}/pipeline/cards/{id}/restore

Restaura um card da lixeira e retorna o card restaurado.

422O pipeline do card não existe mais: o card não pode voltar. Retorna { error }.
DELETE/api/v1/accounts/{account_id}/pipeline/cards/{id}/permanently_delete

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

GET/api/v1/accounts/{account_id}/pipeline/cards/template

Baixa 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.
bash
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
POST/api/v1/accounts/{account_id}/pipeline/cards/import

Envia 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)

NomeTipoObrigatorioDescricao
import_file(body)fileSimArquivo CSV no formato do template (cabeçalhos na 1ª linha). Máximo 10MB.
pipeline_id(body)integerSimID do pipeline alvo onde os cards serão criados (escopado à conta).
bash
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"
200Import enfileirado com sucesso. Os cards são criados em background; import_id é um identificador de correlação. Não há endpoint público de status deste import.
json
{
  "message": "Importação iniciada. Os cards serão criados em segundo plano.",
  "import_id": 42
}
422Arquivo ausente/grande demais (>10MB)/tipo inválido, pipeline_id inválido/ausente, ou falha ao criar o import.
json
{ "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).

GET/api/v1/accounts/{account_id}/pipeline/cards/export

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

bash
# 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
POST/api/v1/accounts/{account_id}/pipeline_cards

Cria 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)

NomeTipoObrigatorioDescricao
titlestringNaoTítulo do deal no nível raiz. Opcional — se omitido, o card usa item_details.title (ou fica null).
pipeline_idintegerSimID do pipeline
pipeline_stagestringSimID 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).
positionintegerNaoPosição do card no estágio
contact_idintegerNaoID do contato associado (resolvido dentro da conta autenticada)
conversation_display_idintegerNaoDisplay ID da conversa associada (per-account, não ID global)
owner_idintegerNaoID do responsável
timer_started_atstringNaoInício do timer (ISO 8601)
timer_durationintegerNaoDuração acumulada do timer
scheduled_atstringNaoData/hora agendada (ISO 8601; rota legacy)
expected_revenuenumberNaoReceita esperada do deal
currencystringNaoCó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.
deadlinestringNaoData prevista de fechamento (ISO 8601)
forecast_close_datestringNaoPrevisão de fechamento (ISO 8601; rota legacy)
descriptionstringNaoDescrição do card
prioritystringNaoPrioridade do card
sourcestringNaoOrigem do card (rota legacy)
item_detailsobjectNaoJSONB 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_attributesobjectNaoAtributos personalizados
qualification_checklistobjectNaoChecklist de qualificação
tagsstring[]NaoTags 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.

422Estágio de destino é uma etapa de Ganho/Perda. A mensagem é localizada no idioma da conta.
json
{
  "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"
    }
  }'
GET/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.

PATCH/api/v1/accounts/{account_id}/pipeline_cards/{id}

Atualiza dados de um card. Body envolto em pipeline_card.

Body (pipeline_card)

NomeTipoObrigatorioDescricao
titlestringNaoTítulo do card
pipeline_stagestringNaoEstá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_idintegerNaoID do pipeline
positionintegerNaoPosição do card no estágio
owner_idintegerNaoID do responsável
contact_idintegerNaoID do contato
conversation_display_idintegerNaoDisplay ID da conversa associada
timer_started_atstringNaoInício do timer (ISO 8601)
timer_durationintegerNaoDuração acumulada do timer
scheduled_atstringNaoData/hora agendada (ISO 8601; rota legacy)
expected_revenuenumberNaoReceita esperada (coluna do card)
currencystringNaoCódigo de moeda com exatamente 3 letras, armazenado em maiúsculas (ex: BRL, USD). Valida o formato, não um catálogo oficial.
deadlinestringNaoData prevista de fechamento (ISO 8601)
forecast_close_datestringNaoData de previsão de fechamento (ISO 8601)
descriptionstringNaoDescrição do card
prioritystringNaoPrioridade do card
sourcestringNaoOrigem do card (rota legacy)
item_detailsobjectNaoJSONB 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_attributesobjectNaoAtributos personalizados (JSONB)
qualification_checklistobjectNaoChecklist de qualificação
tagsstring[]NaoTags 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_id mantendo a mesma chave de estágio) — use POST .../deal_status/mark_won ou POST .../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 enquanto status, won_at e 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.

422Transição de deal tentada pelo PATCH genérico. A mensagem é localizada no idioma da conta; ao sair de uma etapa terminal ela vira 'can only leave a won/lost stage through the reopen endpoint'.
json
{
  "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.value e item_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 via POST .../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.

bash
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"
    }
  }'
DELETE/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).

Mover Card entre Estágios

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

Move um card para outro estágio do pipeline.

Body

NomeTipoObrigatorioDescricao
pipeline_stagestringSimIdentificador 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_reasonstringNaoMotivo da perda quando o destino é um estágio terminal de perda
won_valuenumberNaoValor fechado quando o destino é um estágio terminal de ganho
won_notestringNaoNota do ganho quando o destino é um estágio terminal de ganho
expected_versionintegerNaoO 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; traz allowed_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. Traz expected_version, current_version e current_stage como diagnóstico. Releia, decida de novo e tente — a nova tentativa pode passar.
  • 409 · card_already_closed e cycle_already_recorded — o desfecho já aconteceu; repetir o mesmo movimento nunca vai passar. Ambos trazem requires_fresh_read: true.
  • 422 · expected_version_required (bot sem versão) e invalid_expected_version (valor que não é inteiro não negativo) — a requisição nem foi avaliada. Trazem requires_fresh_read: true e, de propósito, não trazem current_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.

403Agent Bot fora da sua autoridade (inclusive ao tentar reabrir movendo o card). Nada foi alterado.
json
{
  "error": "This agent bot is not allowed to perform this action on pipeline cards",
  "reason": "action_not_allowed_for_bot",
  "allowed_actions": ["cards_move"]
}
bash
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 }'
POST/api/v1/accounts/{account_id}/pipeline_cards/reorder

Reordena 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

NomeTipoObrigatorioDescricao
pipeline_idintegerNaoCampo raiz canônico e recomendado. Define o único pipeline cujos cards serão reordenados.
positionsarraySimArray de objetos { id, position, pipeline_stage }. Entradas sem pipeline_stage são ignoradas.
positions[0].pipeline_idintegerNaoFallback legado, consultado somente quando pipeline_id raiz está ausente. O valor passa a escopar todo o array.
bash
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

POST/api/v1/accounts/{account_id}/pipeline/cards/{id}/deal_status/mark_won

Marca o deal como ganho. Requer que o pipeline tenha etapas Ganho/Perdido habilitadas.

Body (opcional)

NomeTipoObrigatorioDescricao
won_valuenumberNaoValor final fechado (numérico). Valor inválido retorna 422.
won_notestringNaoNota sobre o ganho
winning_offer_indexintegerNaoÍndice da oferta vencedora (quando o card tem ofertas em item_details)
pipeline_product_idintegerNaoProduto do catálogo que foi vendido. É esse vínculo que permite medir receita por produto.
quantitynumberNaoQuantidade vendida. Só faz sentido junto de pipeline_product_id.
unit_valuenumberNaoPreço efetivamente cobrado por unidade — o preço do catálogo é apenas o ponto de partida.
itemsarrayNaoVender 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.

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

POST/api/v1/accounts/{account_id}/pipeline/cards/{id}/deal_status/mark_lost

Marca o deal como perdido.

Body

NomeTipoObrigatorioDescricao
lost_reasonstringNaoMotivo da perda
POST/api/v1/accounts/{account_id}/pipeline/cards/{id}/deal_status/reopen

Reabre 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

GET/api/v1/accounts/{account_id}/pipeline/cards/{id}/timeline

Retorna a timeline de atividades do card.

200Eventos agrupados por data (chave raiz timeline)
json
{
  "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

GET/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachments

Lista anexos de um card.

POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachments

Adiciona um anexo ao card.

Body (multipart/form-data)

NomeTipoObrigatorioDescricao
attachment(body)fileSimArquivo 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.

bash
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"
201Anexo criado e retornado diretamente no corpo.
DELETE/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/attachments/{id}

Remove um anexo. Operação restrita a administradores.

200Retorna { message: 'Attachment deleted successfully' }.
GET/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.

bash
curl -OJ "https://chat.seudominio.com/api/v1/accounts/1/pipeline/cards/5/attachments/31" \
  -H "api_access_token: YOUR_TOKEN"
404Anexo inexistente nesse card: { error: 'Attachment not found' }.

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.

POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/note_attachments

Envia um arquivo para uso em nota (multipart/form-data).

Body (multipart/form-data)

NomeTipoObrigatorioDescricao
attachment(body)fileSimArquivo com no máximo 10 MB. Aceita JPEG, PNG, GIF, WebP, PDF, TXT, CSV, Word, Excel e ZIP.
201Arquivo recebido. Use o id para referenciá-lo na nota.
json
{
  "id": 88,
  "attachment_url": "https://chat.seudominio.com/rails/active_storage/...",
  "message": "Attachment uploaded successfully"
}
422Sem arquivo, tipo não aceito ou acima de 10 MB: { error }.
DELETE/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).

GET/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences

Lista as sequências inscritas no card.

200Sequências do card (chave raiz data)
json
{
  "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"
    }
  ]
}
POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences

Inicia uma sequência no card.

Body

NomeTipoObrigatorioDescricao
definition_idintegerSimID 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.

POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/external_start

Ponto de entrada para integrações externas (ex: n8n). Inicia uma sequência com contexto controlado.

Body

NomeTipoObrigatorioDescricao
definition_idintegerSimID de uma activity_sequence ativa
contextobjectNaoObjeto 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.

PATCH/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/{id}/pause

Pausa a execução ativa da sequência. 422 se não houver execução ativa.

PATCH/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/{id}/resume

Retoma uma sequência pausada. 422 se não houver execução pausada.

PATCH/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/{id}/complete_step

Avança manualmente para a próxima etapa da sequência. 422 se não houver execução ativa ou se não restarem etapas.

DELETE/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/sequences/{id}

Cancela a sequência no card (marca como cancelled). Retorna 200.

GET/api/v1/accounts/{account_id}/pipeline/sequence_analytics

Painel 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

NomeTipoObrigatorioDescricao
days_back(query)integerNaoTamanho da série diária, em dias (padrão 7, máximo 90)
200Envelope { data }.
json
{
  "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.

POST/api/v1/accounts/{account_id}/pipeline/cards/bulk_assign

Atribui responsável para vários cards de uma vez (max 200).

Body

NomeTipoObrigatorioDescricao
item_idsarraySimIDs dos cards (max 200, escopados à conta autenticada)
owner_idintegerNaoID do novo responsável. Se distribution não for usado, omitir owner_id desatribui os cards.
distributionstringNaoround_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.

bash
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 }'
PATCH/api/v1/accounts/{account_id}/pipeline/cards/{id}/assign

Atribui 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

NomeTipoObrigatorioDescricao
owner_idinteger | nullSimID do responsável; null explícito remove o responsável. Sem a chave (e sem o alias user_id) o pedido é 422.
notifybooleanNaoEnviar notificação ao novo responsável
POST/api/v1/accounts/{account_id}/pipeline/bulk_actions/delete

Envia vários cards para a lixeira (soft delete, reversível), um a um, com o mesmo efeito do DELETE individual. Restrito a administradores.

Body

NomeTipoObrigatorioDescricao
card_idsarraySimIDs dos cards (máx. 500). IDs fora da conta ou de pipelines que você não vê são ignorados.
pipeline_stagestringNaoAge só sobre os cards da lista que estão nesse estágio
reasonstringNaoMotivo do descarte, gravado em cada card
bash
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" }'
200Resultado por lote. Se nenhum card foi descartado e houve falhas, o mesmo corpo volta com success: false e status 422.
json
{ "success": true, "deleted": 3, "failed": 0, "failed_items": [] }
400card_ids ausente, que não seja array, ou com mais de 500 IDs: { error }.
POST/api/v1/accounts/{account_id}/pipeline/bulk_actions/set_priority

Define 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

NomeTipoObrigatorioDescricao
card_idsarraySimIDs dos cards (máx. 500), escopados à conta e aos pipelines visíveis
prioritystringSimnone, low, medium, high ou urgent. Outro valor retorna 422 com a lista permitida.
pipeline_stagestringNaoAge só sobre os cards da lista que estão nesse estágio
200Se nenhum card foi atualizado e houve falhas, o mesmo corpo volta com success: false e status 422.
json
{ "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.

GET/api/v1/accounts/{account_id}/pipeline/automations

Lista as automações globais e as dos pipelines visíveis ao usuário. Retorna um array (cada automação inclui stats agregado).

200Lista de automações
json
[
  {
    "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
    }
  }
]
GET/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.

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

Cria 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)

NomeTipoObrigatorioDescricao
namestringSimNome da automação
descriptionstringNaoDescrição
activebooleanNaoSe a automação está ativa
trigger_typestringNaoevent | webhook | scheduled | manual
pipeline_idintegerNaoID do pipeline alvo
triggerobjectNaoConfiguração do gatilho legado (ex: { event, to_stage }). Complementar: não substitui o flow.
conditionsarrayNaoArray de condições para acionar
actionsarrayNaoAções legadas. Complementares: não substituem o flow.
schedule_configobjectNaoConfiguração de agendamento (quando trigger_type = scheduled)
flowobjectSimEditor 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.

422Automação sem fluxo executável. A mensagem é localizada no idioma da conta.
json
{
  "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."
  ]
}
bash
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 }
      }
    }
  }'
PATCH/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.nodes vazio) 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.

DELETE/api/v1/accounts/{account_id}/pipeline/automations/{id}

Remove uma automação.

POST/api/v1/accounts/{account_id}/pipeline/automations/{id}/duplicate

Clona uma automação existente.

GET/api/v1/accounts/{account_id}/pipeline/automations/{id}/export

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

200Documento de exportação.
json
{
  "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

NomeTipoObrigatorioDescricao
event_typestringNaoEvento despachado; para movimento de card use pipeline_item_moved
pipeline_idstringNaoID positivo do pipeline, normalizado para string
from_stage_id / to_stage_idstringNaoChaves dos estágios de origem e destino
patternstringNaoPadrão textual do gatilho
inbox_ids / added_label_idsstring[]NaoArrays de IDs positivos, sem duplicatas após normalização
stage_idsstring[]NaoLista de chaves de estágio
channel_typestringNaoTipo canônico do canal
message_typestringNaoall | incoming | outgoing
only_first_messagebooleanNaoAceita somente boolean real, não "true"/"false" em string
from_status / to_statusstringNaoStatus anterior e novo
label_actionstringNaoany | added | removed
filter_label_idstringNaoID positivo da label, normalizado para string
added_labels / removed_labelsstring[]NaoNomes de labels; cada item deve ser string não vazia

Campos canônicos em condições

NomeTipoObrigatorioDescricao
contact.phone_numberstringNaoTelefone do contato
conversation.assignee_idstringNaoID do responsável da conversa
conversation.team_idstringNaoID do time da conversa
inbox.channel_typestringNaoClasse do canal da inbox
conversation.label_liststring[]NaoLista 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.

POST/api/v1/accounts/{account_id}/pipeline/automations/validate_flow

Valida um flow sem salvar. Envie { flow: { nodes, connections, viewport } }.

200Fluxo válido
json
{ "valid": true, "errors": [], "warnings": [] }
422Exemplo exato de conflito retornado por create, update e validate_flow
json
{
  "valid": false,
  "errors": ["Node trigger-1: conflicting values for pipeline_id, pipelineId"],
  "warnings": []
}
POST/api/v1/accounts/{account_id}/pipeline/automations/import

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

POST/api/v1/accounts/{account_id}/pipeline/automations/{id}/execute

Executa manualmente uma automação.

Body (opcional)

NomeTipoObrigatorioDescricao
conversation_idintegerNaoID interno da conversa usada como contexto
contact_idintegerNaoID do contato usado como contexto quando conversation_id não é enviado
200Execução concluída: { success: true, execution_id, executed_nodes, execution_time }. Falha retorna 422 com success: false, error e execution_id.
POST/api/v1/accounts/{account_id}/pipeline/automations/{id}/dry_run

Executa simulação sem efeito real (teste).

Body (opcional)

NomeTipoObrigatorioDescricao
conversation_idintegerNaoID interno da conversa usada como contexto
contact_idintegerNaoID 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.

POST/api/v1/accounts/{account_id}/pipeline/automations/{id}/validate

Valida a configuração da automação.

GET/api/v1/accounts/{account_id}/pipeline/automations/{id}/executions

Histórico de execuções da automação.

GET/api/v1/accounts/{account_id}/pipeline/automations/all_executions

Histó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

NomeTipoObrigatorioDescricao
status(query)stringNaopending, running, completed, failed ou cancelled. Outro valor (ou all) não filtra.
limit(query)integerNaoPadrão 50, entre 1 e 200
offset(query)integerNaoDeslocamento (padrão 0)
200meta.total conta o total já com o filtro de status aplicado.
json
{
  "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

GET/api/v1/accounts/{account_id}/pipeline/automations/{id}/stats

Métricas de uma automação específica.

GET/api/v1/accounts/{account_id}/pipeline/automations/dashboard

Dashboard de todas as automações.

GET/api/v1/accounts/{account_id}/pipeline/automations/{id}/audit_logs

Logs de auditoria da automação.

GET/api/v1/accounts/{account_id}/pipeline/automations/all_audit_logs

Logs 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

NomeTipoObrigatorioDescricao
audit_action(query)stringNaocreate, update, delete, activate, deactivate, execute, duplicate, import, export ou rotate_webhook_token
user_id(query)integerNaoSomente ações desse usuário
since(query)stringNaoData inicial YYYY-MM-DD (início do dia no fuso da conta)
until(query)stringNaoData final YYYY-MM-DD (fim do dia no fuso da conta)
limit(query)integerNaoPadrão 50, entre 1 e 200
offset(query)integerNaoDeslocamento (padrão 0)
200O endereço IP não é devolvido.
json
{
  "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.

GET/api/v1/accounts/{account_id}/pipeline/automations/{id}/rate_limit

Consumo atual do limite de execuções da automação e da conta na janela vigente. Somente leitura; exige administrador ou pipeline_manage.

200Os limites padrão são 100 execuções por automação e 1.000 por conta, numa janela de 60 minutos; a conta pode ter valores próprios.
json
{
  "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
}
GET/api/v1/accounts/{account_id}/pipeline/automations/rate_limits

Consumo do limite da conta e de cada automação ativa visível ao usuário. Somente leitura; exige administrador ou pipeline_manage.

200automations lista só as automações ativas.
json
{
  "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.

GET/api/v1/accounts/{account_id}/pipeline/automations/{id}/webhook_credentials

Retorna a URL de disparo da automação.

200webhook_url é null enquanto nenhum token foi gerado.
json
{
  "webhook_configured": true,
  "webhook_url": "https://chat.seudominio.com/api/v1/pipeline_automation_webhooks/<token>"
}
422A automação não tem gatilho de webhook: { error }.
POST/api/v1/accounts/{account_id}/pipeline/automations/{id}/rotate_webhook_token

Gera um novo token e devolve a nova URL. A URL anterior deixa de funcionar imediatamente. A rotação fica registrada no log de auditoria.

200Mesmo formato de webhook_credentials, com a URL nova.
json
{ "webhook_configured": true, "webhook_url": "https://chat.seudominio.com/api/v1/pipeline_automation_webhooks/<novo_token>" }
422A automação não tem gatilho de webhook.
503Não foi possível registrar a rotação na auditoria; nada foi alterado.

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.

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

NomeTipoObrigatorioDescricao
token(path)stringSimToken da URL obtida em webhook_credentials
Content-Type(header)stringSimPrecisa ser application/json; outro tipo retorna 415
(corpo)(body)objectNaoObjeto JSON de até 1 MB. Corpo vazio é aceito.
bash
curl -X POST "https://chat.seudominio.com/api/v1/pipeline_automation_webhooks/<token>" \
  -H "Content-Type: application/json" \
  -d '{ "pedido": "1234", "valor": 490 }'
202Aceito; a automação roda em segundo plano.
json
{ "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.

422Token inválido, automação inativa ou sem gatilho de webhook, JSON malformado, corpo que não é objeto, ou falha na execução síncrona: { success: false, error }.
413Corpo acima de 1 MB: { success: false, error: 'Payload too large' }.
415Content-Type diferente de application/json.
POST/api/v1/pipeline_automation_webhooks

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

GET/api/v1/pipeline_automation_webhooks/{token}/verify

Confere se a URL está pronta para receber disparos, sem executar nada. Sem corpo de resposta.

204Token válido, automação ativa e com gatilho de webhook.
404Qualquer outro caso (token inexistente, automação inativa ou sem gatilho de webhook).

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.

GET/api/v1/accounts/{account_id}/pipeline/automation_templates

Lista os templates, os destacados primeiro e depois por nome.

Query

NomeTipoObrigatorioDescricao
category(query)stringNaogeneral, sales, support, marketing, onboarding, notifications ou integrations
locale(query)stringNaoIdioma dos templates (padrão pt-BR)
featured(query)stringNaotrue para trazer só os destacados
order(query)stringNaopopular para ordenar por número de usos
200Lista sem o fluxo; categories traz os identificadores de categoria disponíveis.
json
{
  "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"]
}
GET/api/v1/accounts/{account_id}/pipeline/automation_templates/categories

Lista as categorias com rótulo e quantidade de templates.

200Uma entrada por categoria.
json
{ "categories": [ { "id": "sales", "label": "Vendas", "count": 5 } ] }
GET/api/v1/accounts/{account_id}/pipeline/automation_templates/{id}

Detalhe de um template: os mesmos campos da listagem mais flow, trigger, conditions e actions.

POST/api/v1/accounts/{account_id}/pipeline/automation_templates/{id}/use

Cria na conta uma automação a partir do template. A automação nasce inativa; revise e ative com PATCH /pipeline/automations/{id}.

Body

NomeTipoObrigatorioDescricao
namestringNaoNome da nova automação. Omitido, usa o nome do template.
201A automação criada, no mesmo formato de GET /pipeline/automations/{id}. Falha de validação retorna 422 com { error }.

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.

GET/api/v1/accounts/{account_id}/pipeline/products

Lista o catálogo, ordenado por posição e nome.

Query

NomeTipoObrigatorioDescricao
active_onlybooleanNaoTraz somente os produtos ativos
pipeline_idintegerNaoSomente os disponíveis naquele funil (inclui os irrestritos)
per_pageintegerNaoPadrão 50, máximo 200
offsetintegerNaoPaginaçã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".

GET/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' }.

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

Cria um produto no catálogo.

Body (pipeline_product)

NomeTipoObrigatorioDescricao
namestringSimNome do produto ou serviço
skustringNaoCódigo único na conta. Duplicado retorna 422.
descriptionstringNaoDescrição livre
categorystringNaoCategoria para agrupar no catálogo
default_valuenumberNaoPreço de tabela. Serve de ponto de partida ao fechar — o valor cobrado pode ser outro.
currencystringNaoISO-4217. Omitido, herda a moeda da conta (não o padrão BRL da coluna).
activebooleanNaoPadrão true
positionintegerNaoOrdem no catálogo
pipeline_idsarrayNaoRestringe a esses funis. Vazio ou ausente = disponível em todos.
bash
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.

PATCH/api/v1/accounts/{account_id}/pipeline/products/{id}

Atualiza um produto. Mesmos campos do POST.

DELETE/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.

GET/api/v1/accounts/{account_id}/pipeline/products/performance

Quanto cada produto vendeu num período.

Query

NomeTipoObrigatorioDescricao
won_startstring (YYYY-MM-DD)NaoPrimeiro dia do intervalo. Omitido = desde sempre.
won_endstring (YYYY-MM-DD)NaoÚltimo dia, incluído por inteiro. Anterior a won_start retorna 422.
pipeline_idintegerNaoRestringe a medição a um funil
bash
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.

GET/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/opportunities

Lista as vendas do card em ordem cronológica, com o resumo do acumulado.

POST/api/v1/accounts/{account_id}/pipeline/cards/{card_id}/opportunities

Registra uma venda avulsa no card (o caminho normal é fechar o card como ganho).

Body

NomeTipoObrigatorioDescricao
total_valuenumberNaoValor total. Tem precedência sobre quantity x unit_value.
quantitynumberNaoQuantidade vendida (padrão 1)
unit_valuenumberNaoValor unitário
pipeline_product_idintegerNaoProduto vendido. Id de outra conta retorna 404.
titlestringNaoDescrição do lançamento. Omitido, herda o nome do produto e depois o título do card.
notestringNaoObservação
itemsarrayNaoVender 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.

POST/api/v1/accounts/{account_id}/pipeline/opportunities/{id}/void

Estorna uma venda: ela deixa de contar na receita, mas continua visível no histórico.

Body

NomeTipoObrigatorioDescricao
reasonstringSimMotivo 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.

GET/api/v1/accounts/{account_id}/pipeline/opportunities/report

Relatório de receita da conta: quanto entrou, por funil, por vendedor, por origem e por dia, a partir das vendas registradas.

Query

NomeTipoObrigatorioDescricao
start_date(query)stringNaoData inicial YYYY-MM-DD, pela data da venda (won_at), no fuso da conta
end_date(query)stringNaoData final YYYY-MM-DD, inclusiva (até o fim do dia)
pipeline_id(query)integerNaoRestringe a um funil. Funil inexistente, ou fora do alcance do agente, retorna 404.
200Valores somam só vendas não estornadas; estornos aparecem à parte em voided_by_currency.
json
{
  "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_pipeline com pipeline_id nulo; venda sem vendedor identificado aparece em by_seller com user_id nulo — os detalhamentos fecham com o total.
  • Valores monetários (revenue, average_ticket) e items_sold chegam como string decimal; converta antes de somar. by_source conta vendas por origem do registro (manual, api, automation ou backfill).
  • repeat separa 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_date depois de end_date ou pipeline_id não numérico retornam 422.
  • Faz parte do módulo Oportunidades: com ele desativado na conta, a resposta é 403 com code: 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.

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

Cria um webhook. Body envolto em pipeline_webhook.

Body (pipeline_webhook)

NomeTipoObrigatorioDescricao
namestringSimNome do webhook
urlstringSimURL HTTP ou HTTPS pública que receberá os POSTs. Em produção, prefira HTTPS.
pipeline_idintegerNaoRestringe o webhook a um pipeline da conta. Omitido/null cria um webhook global.
activebooleanNaoAtiva a entrega; padrão true
eventsstring[]SimLista não vazia de eventos disponíveis (ver abaixo). Evento inválido retorna 422.
bash
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"]
    }
  }'
201Webhook criado (o secret é retornado apenas para administradores)
json
{ "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>" }
GET/api/v1/accounts/{account_id}/pipeline/webhooks

Lista os webhooks (admin).

GET/api/v1/accounts/{account_id}/pipeline/webhooks/{id}

Detalhes de um webhook.

PATCH/api/v1/accounts/{account_id}/pipeline/webhooks/{id}

Atualiza name, url, pipeline_id, events ou active.

DELETE/api/v1/accounts/{account_id}/pipeline/webhooks/{id}

Remove o webhook.

POST/api/v1/accounts/{account_id}/pipeline/webhooks/{id}/test

Dispara um POST de teste síncrono para a URL configurada.

PATCH/api/v1/accounts/{account_id}/pipeline/webhooks/{id}/regenerate_secret

Gera 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 criado
  • pipeline_card_updated — card atualizado
  • pipeline_card_deleted — card removido (soft delete)
  • pipeline_card_stage_changed — mudou de estágio (inclui from_stage/to_stage)
  • pipeline_card_won — marcado como ganho; won_at fica em data.card e pre_won_stage em data.card.item_details
  • pipeline_card_lost — marcado como perdido; lost_reason fica em data.card e pre_lost_stage em data.card.item_details
  • pipeline_card_owner_changed — responsável alterado
  • pipeline_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 traz sla_hours (o prazo da etapa), seconds_in_stage (o tempo já gasto) e stage — 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

json
{
  "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:

javascript
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"]));