REST · JSON · HTTPS

Seus leads conectados.
Do formulário ao CRM.

Cadastre contatos, crie negociações e atualize campos do Flexa Go com segurança. Integre n8n, páginas de lançamento e ferramentas de tráfego usando um token exclusivo de cada ambiente.

Tokens por ambiente

  1. Entre no CRM e selecione o ambiente desejado.
  2. Abra Configurações → API, formulários e n8n → Gerar token de API.
  3. Dê um nome, escolha as permissões e a validade: 30, 90 dias ou 1 ano.
  4. Copie o segredo exibido uma única vez. Guarde nas credenciais da integração.
Authorization: Bearer fg_api_SEU_TOKEN
Content-Type: application/json
Idempotency-Key: evento-unico-123

Para a gestora de tráfego, gere um token no ambiente iPhone e outro no ambiente Xiaomi. A chave determina a empresa: não há parâmetro para escolher outro workspace.

Somente proprietários e administradores gerenciam tokens. Um token perde acesso ao expirar, ser revogado ou quando seu emissor perde a permissão de administrador. O MCP usa chaves próprias e continua sendo uma integração distinta.

PermissãoUso
contacts:readConsultar contatos e verificar telefones duplicados
contacts:writeCadastrar e atualizar contatos
leads:readConsultar negociações e sua origem
leads:writeCadastrar negociações, criando seu contato se necessário, e atualizar negociações
fields:readConsultar definições e IDs dos campos
fields:writeCriar definições de campos
pipelines:readConsultar IDs de funis e etapas
contacts:mergeUnir duplicados; também exige contacts:write

Para um relatório de tráfego, use permissões de leitura. Para captura de formulário, adicione leads:write. Permissões de união ou criação de campos devem ser concedidas apenas quando a integração precisar delas.

Primeiro lead em três passos

Base de produção: https://crm.flexago.com.br/api/v1

  1. Valide o token com GET /account.
  2. Consulte GET /pipelines e copie o ID do funil e da etapa desejados.
  3. Envie POST /leads com nome e telefone. Sem funil explícito, a API prefere Vendas e sua primeira etapa aberta.
curl --request POST 'https://crm.flexago.com.br/api/v1/leads' \
  --header 'Authorization: Bearer SEU_TOKEN' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: inscricao-123' \
  --data '{
  "name": "Maria Exemplo",
  "phone": "+5541999990000",
  "title": "Inscrição · lançamento",
  "value_cents": 0,
  "attribution": {
    "source": "meta",
    "medium": "paid_social",
    "utm_source": "instagram",
    "utm_medium": "cpc",
    "utm_campaign": "lancamento",
    "form_id": "form-inscricao",
    "external_id": "inscricao-123"
  }
}'

A primeira criação retorna 201. Quando reutiliza uma negociação aberta do mesmo contato e funil, retorna 200 com deduplicated: true. O contato existe no CRM mesmo que nunca tenha enviado mensagem.

{
  "data": {
    "lead": {
      "id": "UUID_DO_LEAD",
      "contact_id": "UUID_DO_CONTATO",
      "title": "Inscrição · lançamento",
      "value_cents": 0
    },
    "created": true,
    "contact_created": true,
    "deduplicated": false
  },
  "request_id": "UUID_DA_REQUISICAO"
}

Referência de endpoints

Todos os endpoints exigem Bearer token. As operações POST e PATCH exigem Idempotency-Key, exceto a verificação de duplicados.

GET/account

Validar token e consultar ambiente

Retorna id, nome, fuso, permissões e ID do token. Nenhum workspace_id precisa ser informado.

Qualquer token válido

GET/pipelines

Listar funis e etapas

Cada funil retorna suas etapas com id, nome, posição e tipo: open, won ou lost.

Permissão: pipelines:read

GET/custom-fields

Listar campos personalizados

Filtre por ?entity=contact ou ?entity=lead. Use o id retornado para preencher fields.

Permissão: fields:read

POST/custom-fields

Criar definição de campo

Cria um campo e retorna seu UUID estável. Mesmo nome/entidade já existente retorna 409.

Permissão: fields:write
Estrutura JSON de entrada
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "name",
    "entity",
    "type"
  ],
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 100
    },
    "entity": {
      "type": "string",
      "enum": [
        "contact",
        "lead"
      ]
    },
    "type": {
      "type": "string",
      "enum": [
        "text",
        "number",
        "boolean",
        "date",
        "select"
      ]
    },
    "options": {
      "type": "array",
      "maxItems": 100,
      "items": {
        "type": "string"
      },
      "description": "Obrigatório e não vazio para select; deve ficar vazio nos outros tipos."
    }
  }
}

GET/contacts

Consultar contatos

Lista paginada. Filtros: phone, source, created_since, limit e cursor.

Permissão: contacts:read

GET/contacts/{id}

Consultar um contato

Retorna cadastro, fields, preferências e atribuição. ID de outro ambiente retorna 404.

Permissão: contacts:read

POST/contacts

Criar ou reutilizar contato

name e phone obrigatórios. Reutiliza telefone equivalente. Preserva nome/e-mail existentes; fields e atribuição recebidos são mesclados.

Permissão: contacts:write
Estrutura JSON de entrada
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 150
    },
    "phone": {
      "type": "string",
      "description": "Telefone brasileiro completo com DDD ou internacional com +país. Ex.: +5541999990000."
    },
    "email": {
      "type": "string",
      "format": "email",
      "nullable": true
    },
    "fields": {
      "type": "object",
      "description": "Objeto UUID do campo → valor. null limpa apenas o valor indicado. Outros campos são preservados.",
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "nullable": true
          },
          {
            "type": "number"
          },
          {
            "type": "boolean"
          }
        ]
      }
    },
    "attribution": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "source": {
          "type": "string",
          "maxLength": 1000
        },
        "medium": {
          "type": "string",
          "maxLength": 1000
        },
        "campaign": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_source": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_medium": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_campaign": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_content": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_term": {
          "type": "string",
          "maxLength": 1000
        },
        "landing_page": {
          "type": "string",
          "maxLength": 1000
        },
        "referrer": {
          "type": "string",
          "maxLength": 1000
        },
        "external_id": {
          "type": "string",
          "maxLength": 1000
        },
        "form_id": {
          "type": "string",
          "maxLength": 1000
        },
        "ad_id": {
          "type": "string",
          "maxLength": 1000
        },
        "adset_id": {
          "type": "string",
          "maxLength": 1000
        },
        "campaign_id": {
          "type": "string",
          "maxLength": 1000
        },
        "fbclid": {
          "type": "string",
          "maxLength": 1000
        },
        "gclid": {
          "type": "string",
          "maxLength": 1000
        }
      }
    },
    "stop_sends": {
      "type": "boolean"
    }
  },
  "required": [
    "name",
    "phone"
  ]
}

PATCH/contacts/{id}

Atualizar contato

Altera apenas propriedades informadas. Para trocar telefone, exige que o novo telefone não pertença a outro contato.

Permissão: contacts:write
Estrutura JSON de entrada
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 150
    },
    "phone": {
      "type": "string",
      "description": "Telefone brasileiro completo com DDD ou internacional com +país. Ex.: +5541999990000."
    },
    "email": {
      "type": "string",
      "format": "email",
      "nullable": true
    },
    "fields": {
      "type": "object",
      "description": "Objeto UUID do campo → valor. null limpa apenas o valor indicado. Outros campos são preservados.",
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "nullable": true
          },
          {
            "type": "number"
          },
          {
            "type": "boolean"
          }
        ]
      }
    },
    "attribution": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "source": {
          "type": "string",
          "maxLength": 1000
        },
        "medium": {
          "type": "string",
          "maxLength": 1000
        },
        "campaign": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_source": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_medium": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_campaign": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_content": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_term": {
          "type": "string",
          "maxLength": 1000
        },
        "landing_page": {
          "type": "string",
          "maxLength": 1000
        },
        "referrer": {
          "type": "string",
          "maxLength": 1000
        },
        "external_id": {
          "type": "string",
          "maxLength": 1000
        },
        "form_id": {
          "type": "string",
          "maxLength": 1000
        },
        "ad_id": {
          "type": "string",
          "maxLength": 1000
        },
        "adset_id": {
          "type": "string",
          "maxLength": 1000
        },
        "campaign_id": {
          "type": "string",
          "maxLength": 1000
        },
        "fbclid": {
          "type": "string",
          "maxLength": 1000
        },
        "gclid": {
          "type": "string",
          "maxLength": 1000
        }
      }
    },
    "stop_sends": {
      "type": "boolean"
    }
  }
}

POST/contacts/deduplicate

Verificar telefone duplicado

Consulta sem modificar dados. Retorna matches, count e action: create, reuse ou review. Não exige Idempotency-Key.

Permissão: contacts:read
Estrutura JSON de entrada
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "phone"
  ],
  "properties": {
    "phone": {
      "type": "string",
      "description": "Telefone brasileiro completo com DDD ou internacional com +país. Ex.: +5541999990000."
    }
  }
}

POST/contacts/merge

Unir duplicados existentes

União explícita, somente para telefones válidos equivalentes. Preserva auditoria e recusa vínculos ou agendamentos conflitantes. Exige também contacts:write.

Permissão: contacts:merge
Estrutura JSON de entrada
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "keep_id",
    "source_id"
  ],
  "properties": {
    "keep_id": {
      "type": "string",
      "format": "uuid"
    },
    "source_id": {
      "type": "string",
      "format": "uuid"
    }
  }
}

GET/leads

Consultar negociações

Filtros: contact_id, pipeline_id, stage_id, source, created_since, limit e cursor.

Permissão: leads:read

GET/leads/{id}

Consultar uma negociação

Retorna id, contact_id, pipeline_id, stage_id, título, valor, fields e atribuição.

Permissão: leads:read

POST/leads

Capturar lead com contato

Envie contact_id existente OU name/phone e e-mail opcional. Cria contato se necessário; reutiliza a negociação aberta mais recente do mesmo contato e funil.

Permissão: leads:write
Estrutura JSON de entrada
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "contact_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string",
      "maxLength": 150
    },
    "phone": {
      "type": "string",
      "description": "Telefone brasileiro completo com DDD ou internacional com +país. Ex.: +5541999990000."
    },
    "email": {
      "type": "string",
      "format": "email",
      "nullable": true
    },
    "contact_fields": {
      "type": "object",
      "description": "Objeto UUID do campo → valor. null limpa apenas o valor indicado. Outros campos são preservados.",
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "nullable": true
          },
          {
            "type": "number"
          },
          {
            "type": "boolean"
          }
        ]
      }
    },
    "title": {
      "type": "string",
      "maxLength": 150
    },
    "pipeline_id": {
      "type": "string",
      "format": "uuid"
    },
    "stage_id": {
      "type": "string",
      "format": "uuid"
    },
    "value_cents": {
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "fields": {
      "type": "object",
      "description": "Objeto UUID do campo → valor. null limpa apenas o valor indicado. Outros campos são preservados.",
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "nullable": true
          },
          {
            "type": "number"
          },
          {
            "type": "boolean"
          }
        ]
      }
    },
    "attribution": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "source": {
          "type": "string",
          "maxLength": 1000
        },
        "medium": {
          "type": "string",
          "maxLength": 1000
        },
        "campaign": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_source": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_medium": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_campaign": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_content": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_term": {
          "type": "string",
          "maxLength": 1000
        },
        "landing_page": {
          "type": "string",
          "maxLength": 1000
        },
        "referrer": {
          "type": "string",
          "maxLength": 1000
        },
        "external_id": {
          "type": "string",
          "maxLength": 1000
        },
        "form_id": {
          "type": "string",
          "maxLength": 1000
        },
        "ad_id": {
          "type": "string",
          "maxLength": 1000
        },
        "adset_id": {
          "type": "string",
          "maxLength": 1000
        },
        "campaign_id": {
          "type": "string",
          "maxLength": 1000
        },
        "fbclid": {
          "type": "string",
          "maxLength": 1000
        },
        "gclid": {
          "type": "string",
          "maxLength": 1000
        }
      }
    }
  },
  "oneOf": [
    {
      "required": [
        "contact_id"
      ],
      "not": {
        "anyOf": [
          {
            "required": [
              "name"
            ]
          },
          {
            "required": [
              "phone"
            ]
          },
          {
            "required": [
              "email"
            ]
          }
        ]
      }
    },
    {
      "required": [
        "name",
        "phone"
      ],
      "not": {
        "required": [
          "contact_id"
        ]
      }
    }
  ]
}

PATCH/leads/{id}

Atualizar negociação

Permite title, pipeline_id, stage_id, value_cents, fields e attribution. Para mover de funil, envie uma etapa pertencente ao destino ou será usada sua primeira etapa aberta.

Permissão: leads:write
Estrutura JSON de entrada
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "title": {
      "type": "string",
      "maxLength": 150
    },
    "pipeline_id": {
      "type": "string",
      "format": "uuid"
    },
    "stage_id": {
      "type": "string",
      "format": "uuid"
    },
    "value_cents": {
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "fields": {
      "type": "object",
      "description": "Objeto UUID do campo → valor. null limpa apenas o valor indicado. Outros campos são preservados.",
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "nullable": true
          },
          {
            "type": "number"
          },
          {
            "type": "boolean"
          }
        ]
      }
    },
    "attribution": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "source": {
          "type": "string",
          "maxLength": 1000
        },
        "medium": {
          "type": "string",
          "maxLength": 1000
        },
        "campaign": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_source": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_medium": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_campaign": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_content": {
          "type": "string",
          "maxLength": 1000
        },
        "utm_term": {
          "type": "string",
          "maxLength": 1000
        },
        "landing_page": {
          "type": "string",
          "maxLength": 1000
        },
        "referrer": {
          "type": "string",
          "maxLength": 1000
        },
        "external_id": {
          "type": "string",
          "maxLength": 1000
        },
        "form_id": {
          "type": "string",
          "maxLength": 1000
        },
        "ad_id": {
          "type": "string",
          "maxLength": 1000
        },
        "adset_id": {
          "type": "string",
          "maxLength": 1000
        },
        "campaign_id": {
          "type": "string",
          "maxLength": 1000
        },
        "fbclid": {
          "type": "string",
          "maxLength": 1000
        },
        "gclid": {
          "type": "string",
          "maxLength": 1000
        }
      }
    }
  }
}

Campos personalizados por ID

Os campos existentes já têm UUIDs. Consulte GET /custom-fields ou copie o ID em Configurações → API → IDs dos campos. O ID de um campo de contato não pode ser usado numa negociação, nem em outro ambiente.

{
  "fields": {
    "11111111-1111-4111-8111-111111111111": "Instagram",
    "22222222-2222-4222-8222-222222222222": true
  }
}

Use JSON nativo: número sem aspas, booleano true/false, data AAAA-MM-DD e texto nas opções já cadastradas para um campo select. Enviar null limpa o valor daquele ID. Outros valores são preservados.

Na criação de lead, fields preenche a negociação e contact_fields preenche o contato. O valor principal da negociação usa value_cents: R$ 150,00 = 15000. Consulte o esquema antes de preencher um campo de valor já existente.

Desduplicação que protege a base

A identidade é o telefone normalizado dentro do ambiente. Telefones brasileiros precisam estar completos. A comparação reconhece aliases históricos com e sem o nono dígito; novos celulares devem conter o nono dígito correto.

  1. POST /contacts/deduplicate informa se deve criar, reutilizar ou revisar.
  2. POST /contacts cria ou reutiliza o contato automaticamente.
  3. POST /leads reutiliza a negociação aberta mais recente do contato no funil selecionado. Se todas estiverem encerradas, cria uma nova.

Nome e e-mail nunca são usados sozinhos para unir pessoas. Cadastros antigos com múltiplos telefones equivalentes geram conflito para revisão. A união por /contacts/merge é explícita, auditada e pode ser recusada se houver públicos, trabalhos ou agendamentos conflitantes.

Repetir um POST não move uma negociação aberta de etapa nem sobrescreve seu título, valor, nome ou e-mail. Use PATCH quando quiser alterar esses dados explicitamente. Campos e atribuição enviados no POST são mesclados.

Meta, orgânico e campanhas

Envie attribution na criação ou atualização. A API preserva first_touch e atualiza last_touch, com horário de captura. A origem é declarada pela integração; a API não prova sozinha que uma visita veio da Meta.

{
  "attribution": {
    "source": "meta",
    "medium": "paid_social",
    "utm_source": "instagram",
    "utm_medium": "cpc",
    "utm_campaign": "lancamento",
    "form_id": "form-inscricao",
    "external_id": "inscricao-123"
  }
}

Chaves aceitas: source, medium, campaign, utm_source, utm_medium, utm_campaign, utm_content, utm_term, landing_page, referrer, external_id, form_id, ad_id, adset_id, campaign_id, fbclid, gclid.

Para analisar entradas pagas: GET /leads?source=meta&created_since=2026-10-01T00%3A00%3A00-03%3A00. O filtro source considera a última origem registrada; a primeira permanece disponível em cada cadastro. Usar essas UTMs não envia eventos para o pixel nem para a Conversions API da Meta.

Configurar no n8n

  1. Crie uma credencial Header Auth, nome Authorization, valor Bearer SEU_TOKEN.
  2. Use Webhook ou o gatilho do seu formulário e conecte um nó HTTP Request.
  3. Selecione POST, URL https://crm.flexago.com.br/api/v1/leads e Authentication: Generic Credential Type → Header Auth.
  4. Envie JSON com name, phone, pipeline_id, stage_id e attribution.
  5. Adicione Idempotency-Key com o ID estável da inscrição, por exemplo form-{{$json.submission_id}}.
  6. Em 429, aguarde Retry-After; em erro transitório, repita com a mesma chave e o mesmo JSON. Não repita automaticamente erros 4xx de validação.

O arquivo OpenAPI descreve os contratos e a coleção Postman contém os endpoints. Nenhum download inclui credenciais.

Formulário → CRM → grupo de WhatsApp

O visitante envia nome e telefone ao seu servidor ou a um webhook do n8n. Esse servidor salva o lead na API e, após o sucesso, devolve o link do grupo para a página redirecionar.

// Execute no servidor. FLEXAGO_TOKEN deve ser uma variável secreta.
const response = await fetch('https://crm.flexago.com.br/api/v1/leads', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.FLEXAGO_TOKEN,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'form-' + submission.id
  },
  body: JSON.stringify({
    name: submission.name,
    phone: submission.phone,
    attribution: { source: 'organico', form_id: 'lancamento' }
  })
});
const result = await response.json();
if (!response.ok) throw new Error(result.error.message);
// Somente após salvar com sucesso, redirecione para o link do grupo:
// return Response.redirect(process.env.WHATSAPP_GROUP_URL, 303);

O token deve ficar em uma variável secreta no servidor ou no cofre do n8n. Não coloque o token no HTML, JavaScript público, URL do formulário ou link do grupo. Proteja o formulário público contra spam e valide seus campos.

O cadastro não depende de mensagem recebida e não cria uma conversa artificial. Entrar no grupo ou preencher o formulário não abre a janela de 24 horas de uma conversa individual. Para contato futuro, registre a autorização adequada e use os canais, regras e templates exigidos pelo provedor. Esta API não executa disparos.

Paginação, repetição e erros

Listas usam limit de 1 a 100 (padrão 50). Passe o pagination.next_cursor na próxima requisição e preserve os mesmos filtros. has_more: false indica o fim. Novos registros inseridos durante a leitura podem exigir uma nova consulta desde a primeira página.

{
  "data": [],
  "pagination": {
    "limit": 50,
    "has_more": false,
    "next_cursor": null
  },
  "request_id": "UUID_DA_REQUISICAO"
}

Idempotency-Key dura 24 horas por token. A mesma chave e o mesmo conteúdo retornam o resultado anterior, inclusive após timeout; o cabeçalho Idempotent-Replayed: true identifica a repetição. Outra combinação de dados com a mesma chave retorna 409. Depois de 24 horas, o upsert por telefone/negociação ainda ajuda a evitar duplicidade, mas não substitui uma chave única por evento.

HTTPSignificado / ação
200 / 201Sucesso / criação
400Formato inválido, UUID ou propriedade desconhecida
401Token inválido, expirado ou revogado
403Permissão insuficiente ou chamada de navegador de outra origem
404Registro ausente ou pertencente a outro ambiente
405Método não permitido
409Duplicidade, conflito de atualização, união insegura ou chave usada com dados diferentes
413 / 415Corpo excede 32 KB / envie application/json
422Campo, opção, telefone ou relação inválidos
429Aguarde o Retry-After: 60 segundos
503Serviço temporariamente indisponível; preserve a chave de idempotência

O limite é de 60 requisições por minuto por token em cada ponto da rede Cloudflare. É uma proteção de requisições HTTP, distinta dos limites de envio do WhatsApp. Máximo: 32 KB por corpo e 40 campos por entidade. Sempre guarde o request_id de erros para suporte.

O que está disponível nesta versão

A API v1 cobre cadastro e consulta de contatos e negociações, atualização de dados, consulta/criação de campos personalizados, IDs de funis e etapas, verificação/união de duplicados, atribuição de campanhas e tokens por ambiente.

Conversas, mídias, envio de mensagens, templates, flows, disparos, agenda clínica e permissões de usuários continuam nos fluxos próprios do CRM. Não são endpoints públicos desta versão. Não há DELETE público nem webhook de saída nesta API.

Os contratos foram organizados com referência à captura de leads do Kommo e aos campos por ID. O formato e os IDs do Flexa Go são próprios; não são compatíveis automaticamente com a API do Kommo.