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 e usar bulk_assign 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, remover, duplicar, executar e usar dry_run 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"
  }
]
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.

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

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

  • titleobrigató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.
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. Continua sendo o caminho recomendado para mover cards, inclusive 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_revenuecoluna 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

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. Nada mudou aqui — é o caminho recomendado para mover cards por API, inclusive para fechar um deal em uma única chamada.

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.

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

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.

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. 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 um responsável a um card específico.

Body

NomeTipoObrigatorioDescricao
owner_idintegerNaoID do responsável. Omita para desatribuir o card.
notifybooleanNaoEnviar notificação ao novo responsável

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.

Alterações de automação são administrativas

Criar, atualizar, remover, importar, duplicar ou executar automações exige uma conta de administrador. Usuários autenticados podem consultar as automações 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
    }
  }
]
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.

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

Execução e simulação são administrativas

execute e dry_run exigem administrador. 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.

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.

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

Catálogo e razão de vendas fazem 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.

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

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

Sem Oportunidades ativado na conta, estes endpoints respondem 403 com code: pipeline_opportunities_disabled. Fechar um card como ganho continua funcionando: o card guarda a data e o valor, mas a resposta de mark_won não traz o bloco opportunity e pipeline_product_id é ignorado.

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.

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