# API LeadGo — arquivo para IA

> Documentação completa da API v1.2.0 do LeadGo: criar e operar campanhas de Meta Ads (Facebook e Instagram) pelo seu sistema, receber e devolver os leads, acompanhar resultados, públicos, a saúde da conta e o Gestor IA, com as mesmas rotinas do painel. Este arquivo foi escrito para uma IA de programação implementar a integração.

- Endereço base: https://leadgo.com.br/api/v1
- Documentação: https://leadgo.com.br/docs/api
- OpenAPI 3.1: https://leadgo.com.br/docs/api/openapi.json

## Como usar este arquivo

Cole o arquivo inteiro na conversa com a sua IA (ChatGPT, Claude, Gemini, Cursor, Copilot) e peça, por exemplo:

```text
Implemente a integração do meu sistema com a API do LeadGo seguindo este documento.
Meu sistema: [linguagem e framework]. Quero: [o que a integração deve fazer].
Siga as "Instruções para a IA" e o "Roteiro de implementação".
```

## Instruções para a IA

- Use o endereço base https://leadgo.com.br/api/v1 e envie em toda chamada `Authorization: Bearer <token>` e `Accept: application/json`.
- Leia o token de uma variável de ambiente (LEADGO_TOKEN) ou de um cofre de segredos. Nunca o coloque no front-end, em repositório ou em log.
- Centralize as chamadas num único cliente HTTP, com timeout de 30 s (180 s em POST /campaign-briefs) e o registro do cabeçalho X-Request-Id de cada resposta.
- Trate erros pelo campo `code` (estável), nunca pelo texto de `message`. Em 422 `validation_failed`, mostre as mensagens de `errors` por campo.
- Novas tentativas: 429 espera o cabeçalho Retry-After; 502 e 503 repetem com espera exponencial (1 s, 2 s, 4 s... até 5 tentativas); 409 `idempotency_in_progress` repete em alguns segundos. Os demais 4xx não se repetem: corrija o pedido.
- Em toda rota que aceita Idempotency-Key (criar, duplicar, reenviar), gere um UUID por operação e reuse o mesmo UUID nas novas tentativas da mesma operação.
- Envie a mídia primeiro (POST /media) e use o id devolvido em creatives[].media_id (arte ou vídeo) ou em creatives[].cards[].media_id (imagem de cada cartão do carrossel). Mídia por URL só serve quando o status ficar ready.
- Monte creatives com um item por anúncio que o usuário preencheu: arte e vídeo com media_id, carrossel com cards (um por campanha). Nunca envie um item vazio para um anúncio que não existe.
- Antes de criar de verdade, valide o corpo com `?validate_only=true` (nada é criado e a resposta mostra os padrões aplicados).
- UTMs: em todo objetivo (inclusive cadastro, WhatsApp e post impulsionado), os 4 padrão (utm_source, utm_medium, utm_campaign e utm_content) vão sempre. Envie em tracking.url_tags só os extras; um extra com a chave de um dos 4 troca o valor dele, e [] quer dizer "sem extras", nunca "sem UTMs".
- Pixel: todo anúncio sai com "Eventos do site" marcado, e o Pixel é obrigatório. Sem tracking.pixel_id (aceito em qualquer objetivo), vale o Pixel padrão da conta ou o único que ela enxerga. Com vários e nenhum padrão, a criação volta 422 em tracking.pixel_id: peça ao usuário para escolher um de GET /ad-account/pixels e envie em tracking.pixel_id.
- A criação é assíncrona: POST /campaigns responde 202 com status creating. Acompanhe pelo webhook (campaign.published ou campaign.failed) ou consultando GET /campaigns/{id} a cada 15 a 30 segundos até o status mudar.
- Dinheiro vai em centavos inteiros nos campos amount_cents e value_cents; métricas voltam em unidades da moeda. Datas de agenda em AAAA-MM-DD no fuso da conta (GET /me).
- Não fixe listas no código: objetivos, botões, países, eventos e limites vêm de GET /reference.
- Pagine listas por cursor seguindo links.next até vir null.
- No receptor de webhooks: confira a assinatura (seção "Conferindo a assinatura") com comparação em tempo constante, recuse timestamps com mais de 5 minutos, descarte eventos repetidos pelo X-Leadgo-Event-Id, responda 2xx em até 15 s e processe depois.
- Respeite os limites (seção "Limites"): o servidor devolve 429, e a Meta pode bloquear contas que criam campanhas demais em pouco tempo.
- Peça ao usuário só os escopos que a integração usa. leads:read lê dados pessoais (nome, e-mail, telefone): guarde o mínimo, nunca registre em log e atenda ao pedido de exclusão com DELETE /leads/{id}.
- Leads: receba pelos webhooks lead.created, lead.scored e lead.status_changed (GET /leads só para a carga inicial e conferências) e devolva cada avanço do funil com PATCH /leads/{id}, para a Meta aprender com as vendas.
- Antes de criar campanhas, confira GET /ad-account/health: com status blocked (pagamento, conta restrita) a Meta recusa os anúncios. Assine alert.created para avisar o usuário do que pede atenção.
- Para remarketing, crie o público em POST /audiences e use o platform_audience_id em targeting.audience_id ou targeting.excluded_audience_ids; nunca invente ids de público.
- Interesses só com os ids de GET /targeting/interests. Para exigir um interesse de cada grupo (E), use targeting.narrow_interests, nunca com advantage_audience true, e confira POST /targeting/reach-estimate antes de criar (warning avisa quando o público pode não bastar).
- Gestor IA: só aprove, recuse ou desfaça ações (rotas /brain/*) por decisão do usuário do seu sistema, e só quando can_decide (ou can_rollback) for true.
- Ignore campos desconhecidos nas respostas e nos webhooks: a v1 pode ganhar campos novos sem aviso.
- Escreva testes da integração simulando as respostas documentadas aqui, incluindo os erros.

## Roteiro de implementação

Implemente só as etapas que o pedido do usuário precisa, nesta ordem:

1. Cliente HTTP com autenticação, timeout, novas tentativas e tratamento de erros pelo `code`.
2. Teste de conexão com GET /me: conta, fuso, autor e escopos do token.
3. Leitura e cache (algumas horas) de GET /reference e de GET /ad-account (moeda, verba mínima, Pixel padrão); conferência de GET /ad-account/health.
4. Envio de mídias com POST /media (arquivo ou URL) e espera do status ready.
5. Criação de campanhas com POST /campaigns: validate_only primeiro (e, se quiser mostrar ao usuário, POST /campaigns/preview e POST /targeting/reach-estimate), depois a criação com Idempotency-Key.
6. Acompanhamento da publicação: webhook campaign.published/campaign.failed ou consulta a GET /campaigns/{id}.
7. Operação: pausar, ligar, mudar verba e excluir campanhas (PATCH e DELETE /campaigns/{id}), conjuntos (/ad-sets/{id}) e anúncios (/ads/{id}, inclusive trocar texto, imagem e UTMs).
8. Resultados com GET /reports/account, GET /reports/campaigns, GET /campaigns/{id}/metrics e GET /ads/{id}/metrics.
9. Leads: webhooks lead.*, carga inicial por GET /leads e devolução do funil por PATCH /leads/{id}.
10. Públicos, personas e padrões, se a integração monta a segmentação (POST /audiences, /personas, /templates).
11. Alertas e Gestor IA, se o usuário quer acompanhar a conta pelo seu sistema (GET /alerts, /brain/reports e as decisões).
12. Receptor de webhooks com conferência da assinatura e cadastro com POST /webhooks, assinando só os eventos usados.
13. Testes automatizados de cada etapa, com as respostas e os erros documentados aqui.

## Introdução

O que a API faz, o endereço base e a primeira campanha em 3 chamadas.

### Visão geral

A API do LeadGo cria e opera campanhas de Meta Ads (Facebook e Instagram) a partir do seu sistema: CRM, loja virtual, ERP ou automações como n8n e Make. Ela usa exatamente as mesmas rotinas do painel: as mesmas validações, a mesma fila de publicação e os mesmos avisos. Uma campanha criada pela API aparece no painel, entra no Gestor IA e pode ser editada por lá.

Endereço base:

```text
https://leadgo.com.br/api/v1
```

- Requisições e respostas em JSON (UTF-8), com campos em snake_case. As mensagens de erro vêm em português.
- Dinheiro em centavos nos campos amount_cents (5000 = R$ 50,00), na moeda da conta de anúncio. As métricas vêm em unidades (312.5 = R$ 312,50).
- Datas no formato ISO 8601 (2026-10-28T15:00:00+00:00); datas de agenda em AAAA-MM-DD, no fuso da conta.
- Toda resposta traz X-Request-Id. Informe-o ao suporte para acharmos a chamada.
- Os exemplos usam cURL, com o token na variável de ambiente LEADGO_TOKEN.

### Sua primeira campanha em 3 chamadas

1. Envie a imagem ou o vídeo. A resposta traz o id da mídia (por URL, aguarde o status ready ou o aviso media.ready).

POST /media:

```bash
curl -X POST "https://leadgo.com.br/api/v1/media" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -F "file=@outubro.jpg"
```

2. Crie a campanha com o id da mídia. Use uma Idempotency-Key: se a rede cair e você repetir, ela não é criada duas vezes. Para só conferir o corpo, acrescente ?validate_only=true.

POST /campaigns:

```bash
curl -X POST "https://leadgo.com.br/api/v1/campaigns" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Promoção de outubro",
    "goal": "traffic",
    "status": "paused",
    "budget": {
        "type": "daily",
        "amount_cents": 5000
    },
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 25,
        "age_max": 55,
        "gender": "all"
    },
    "link_url": "https://loja.exemplo.com.br/outubro",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Outono com 30% off",
            "body": "Peças selecionadas com frete grátis até domingo.",
            "call_to_action": "SHOP_NOW"
        }
    ]
}'
```

3. Acompanhe a publicação. A resposta é 202 com status creating: a Meta recebe a campanha em segundos ou minutos. O resultado chega no seu webhook (campaign.published ou campaign.failed) ou consultando a campanha:

GET /campaigns/{id}:

```bash
curl -X GET "https://leadgo.com.br/api/v1/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Depois de publicada, a Meta revisa os anúncios (status pending_review, geralmente menos de 24 h). Com status=paused (o padrão), ela fica pausada depois de aprovada: ligue com PATCH /campaigns/{id} {"status": "active"}.

## Autenticação e agências

Como criar o token, os escopos e como agências operam cada cliente.

### Autenticação

Crie o token no painel, em API (menu Configurações). É preciso ser administrador da conta. O token aparece uma única vez: guarde-o num cofre de senhas ou nas variáveis de ambiente do seu sistema, nunca no front-end. Envie-o em todas as chamadas:

Cabeçalho:

```text
Authorization: Bearer lgo_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Cada token opera uma única conta, em nome de quem o criou: o token nunca faz mais do que essa pessoa faria no painel. Se ela sair da conta ou perder a permissão, o token para (token_owner_lost_access). Escolha só os escopos de que a integração precisa:

| Escopo | Permite |
| --- | --- |
| campaigns:read | Ler campanhas e consultas: Conta de anúncio (saúde, saldo e alertas), campanhas, conjuntos, anúncios, públicos, personas, padrões, segmentação e listas de referência. |
| campaigns:write | Criar e alterar campanhas: Publicar, pausar, ativar, mudar verba, duplicar e excluir campanhas, conjuntos e anúncios; criar públicos, personas e padrões. |
| media:write | Enviar mídias: Subir imagens e vídeos para os anúncios. |
| reports:read | Ler métricas: Resultados e gastos da conta, das campanhas e dos anúncios. |
| ai:draft | Rascunho pela IA: Montar uma campanha a partir de um briefing e sugerir personas. |
| webhooks:manage | Gerenciar webhooks: Cadastrar URLs que recebem os avisos das campanhas, dos leads e do Gestor IA. |
| account:manage | Gerenciar a conta de anúncio: Limite de gasto da conta de anúncio e alertas. |
| leads:read | Ler leads: Leads dos formulários, com as respostas, o contato e o histórico (dados pessoais). |
| leads:write | Atualizar leads: Mudar o status do lead, registrar venda ou perda, notas e exclusão (LGPD). |
| brain:read | Ler o Gestor IA: Relatórios e ações propostas pelo Gestor IA. |
| brain:manage | Decidir no Gestor IA: Pedir análise, aprovar, recusar e desfazer ações do Gestor IA. |

- Até 10 tokens ativos por conta: use um por integração e revogue só o que vazar.
- Validade de 30 dias, 90 dias, 1 ano ou sem validade. Token vencido responde token_expired.
- Teste com GET /me: ele diz a conta, o autor e os escopos do token.
- A conta precisa estar ativa e em dia. Com fatura vencida além da carência, as chamadas respondem 402 (payment_overdue).

### Agências e clientes

Gestores e agências criam um token por cliente: no painel, entre na conta do cliente (Clientes › Operar) e crie o token na tela API. Esse token só enxerga e opera aquele cliente: campanhas, mídias, webhooks e métricas ficam separados por conta, como no painel.

O token é da pessoa da agência que o criou. Se o cliente deixar de ser gerenciado pela agência, o token para de funcionar.

## Exemplos e ciclo de vida

Corpos completos de criação por objetivo e os status pelos quais a campanha passa.

### Exemplos por objetivo

Tráfego para o site: O caso mais simples: leva pessoas para uma página.

POST /campaigns:

```json
{
    "name": "Promoção de outubro",
    "goal": "traffic",
    "status": "paused",
    "budget": {
        "type": "daily",
        "amount_cents": 5000
    },
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 25,
        "age_max": 55,
        "gender": "all"
    },
    "link_url": "https://loja.exemplo.com.br/outubro",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Outono com 30% off",
            "body": "Peças selecionadas com frete grátis até domingo.",
            "call_to_action": "SHOP_NOW"
        }
    ]
}
```

Vendas no site (conversões): Otimiza para o evento do Pixel. Sem pixel_id, vale o Pixel padrão da conta (ou o único que ela enxerga).

POST /campaigns:

```json
{
    "name": "Black Friday | Vendas",
    "goal": "conversions",
    "budget": {
        "type": "daily",
        "amount_cents": 15000
    },
    "schedule": {
        "start_date": null,
        "end_date": null
    },
    "tracking": {
        "pixel_id": "123456789012345",
        "conversion_event": "PURCHASE"
    },
    "targeting": {
        "cities": [
            {
                "key": "269969",
                "name": "São Paulo, São Paulo",
                "country_code": "BR"
            }
        ],
        "interests": [
            {
                "id": "6003107902433",
                "name": "Compras online"
            }
        ]
    },
    "link_url": "https://loja.exemplo.com.br/black-friday",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Até 50% off",
            "body": "A Black Friday começou. Estoque limitado.",
            "call_to_action": "SHOP_NOW"
        },
        {
            "media_id": "01jbx3n2p6s0d4g8j2m6q0t4wy",
            "headline": "Frete grátis hoje",
            "body": "Só até meia-noite.",
            "call_to_action": "BUY_NOW"
        }
    ]
}
```

Conversas no WhatsApp: Abre uma conversa no WhatsApp da empresa com a mensagem pronta. Sem link nem botão.

POST /campaigns:

```json
{
    "name": "Clínica | WhatsApp",
    "goal": "whatsapp_messages",
    "budget": {
        "amount_cents": 4000
    },
    "targeting": {
        "cities": [
            {
                "key": "2490299",
                "name": "Campinas, São Paulo",
                "country_code": "BR"
            }
        ]
    },
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Agende sua avaliação",
            "body": "Fale com a gente pelo WhatsApp e garanta seu horário."
        }
    ],
    "whatsapp": {
        "greeting": "Olá! Quer agendar uma avaliação?",
        "prefill": "Quero agendar uma avaliação.",
        "phone_number": "+5519999998888"
    }
}
```

Carrossel com UTMs: Várias imagens, cada uma com título e link, deslizando no mesmo anúncio. Os 4 UTMs padrão vão sempre; aqui, utm_campaign troca o valor do padrão e utm_term se soma a eles.

POST /campaigns:

```json
{
    "name": "Coleção de verão | Carrossel",
    "goal": "traffic",
    "budget": {
        "amount_cents": 6000
    },
    "link_url": "https://loja.exemplo.com.br/verao",
    "format": "carousel",
    "creatives": [
        {
            "headline": "Coleção de verão",
            "body": "Deslize e escolha a sua peça favorita.",
            "call_to_action": "SHOP_NOW"
        }
    ],
    "carousel": {
        "cards": [
            {
                "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
                "headline": "Vestidos",
                "link_url": "https://loja.exemplo.com.br/verao/vestidos"
            },
            {
                "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
                "headline": "Sandálias",
                "description": "A partir de R$ 89",
                "link_url": "https://loja.exemplo.com.br/verao/sandalias"
            }
        ]
    },
    "tracking": {
        "url_tags": [
            {
                "key": "utm_campaign",
                "value": "verao"
            },
            {
                "key": "utm_term",
                "value": "carrossel"
            }
        ]
    }
}
```

Arte, vídeo e carrossel na mesma campanha: Cada item de creatives é um anúncio do mesmo conjunto: arte ou vídeo com media_id, carrossel com cards (um por campanha). Envie só os itens que existirem. Do segundo item em diante, o título, o texto, a descrição e o botão que faltarem vêm do primeiro.

POST /campaigns:

```json
{
    "name": "Coleção de verão | Arte, vídeo e carrossel",
    "goal": "traffic",
    "budget": {
        "amount_cents": 9000
    },
    "link_url": "https://loja.exemplo.com.br/verao",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Coleção de verão",
            "body": "Peças leves com frete grátis até domingo.",
            "call_to_action": "SHOP_NOW"
        },
        {
            "media_id": "01jbx3n2p6s0d4g8j2m6q0t4wy"
        },
        {
            "body": "Deslize e escolha a sua peça favorita.",
            "cards": [
                {
                    "media_id": "01jbx3p5r9t3e7h1k5n9r3v7c2",
                    "headline": "Vestidos",
                    "link_url": "https://loja.exemplo.com.br/verao/vestidos"
                },
                {
                    "media_id": "01jbx3q8v2w6a0c4f8j2m6p0s4",
                    "headline": "Sandálias",
                    "description": "A partir de R$ 89",
                    "link_url": "https://loja.exemplo.com.br/verao/sandalias"
                }
            ]
        }
    ]
}
```

Remarketing sem quem já comprou: Anuncia para quem visitou o site nos últimos 30 dias (público de POST /audiences) e deixa de fora os compradores, num raio de 25 km de São Paulo.

POST /campaigns:

```json
{
    "name": "Remarketing | Visitantes 30 dias",
    "goal": "traffic",
    "budget": {
        "amount_cents": 3000
    },
    "targeting": {
        "cities": [
            {
                "key": "269969",
                "name": "São Paulo, São Paulo",
                "country_code": "BR",
                "radius_km": 25
            }
        ],
        "audience_id": "23859876543210987",
        "excluded_audience_ids": [
            "23851234567890123"
        ],
        "languages": [
            "pt"
        ]
    },
    "link_url": "https://loja.exemplo.com.br/outubro",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Ainda pensando?",
            "body": "As peças que você viu estão com 10% off até domingo.",
            "call_to_action": "SHOP_NOW"
        }
    ]
}
```

Público com interesses em E: Só quem tem um dos interesses principais e também um de cada grupo de narrow_interests: aqui, quem se interessa por investimento imobiliário e também por condomínio fechado ou casa de praia.

POST /campaigns:

```json
{
    "name": "Lançamento | Lotes na praia",
    "goal": "traffic",
    "budget": {
        "amount_cents": 8000
    },
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 35,
        "interests": [
            {
                "id": "6003012345678",
                "name": "Investimento imobiliário"
            }
        ],
        "narrow_interests": [
            [
                {
                    "id": "6003023456789",
                    "name": "Condomínio fechado"
                },
                {
                    "id": "6003034567890",
                    "name": "Casa de praia"
                }
            ]
        ]
    },
    "link_url": "https://imobiliaria.exemplo.com.br/lancamento",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Lotes a 300 m da praia",
            "body": "Condomínio fechado com marina. Veja as condições de lançamento.",
            "call_to_action": "LEARN_MORE"
        }
    ]
}
```

Vendas com custo por resultado: Conversões de compra com teto de R$ 40 por venda (COST_CAP), só no feed e nos stories do Instagram.

POST /campaigns:

```json
{
    "name": "Vendas | Custo por compra R$ 40",
    "goal": "conversions",
    "budget": {
        "amount_cents": 20000
    },
    "tracking": {
        "pixel_id": "123456789012345",
        "conversion_event": "PURCHASE"
    },
    "bidding": {
        "strategy": "COST_CAP",
        "amount_cents": 4000
    },
    "placements": [
        "instagram_feed",
        "instagram_stories"
    ],
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 25,
        "age_max": 54
    },
    "link_url": "https://loja.exemplo.com.br/mais-vendidos",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Os mais vendidos",
            "body": "Frete grátis acima de R$ 199.",
            "call_to_action": "SHOP_NOW"
        }
    ]
}
```

Cadastros com formulário instantâneo: O formulário fica no Facebook/Instagram (sem site). Use lead_form para um novo ou lead_form_id para reaproveitar um da conta.

POST /campaigns:

```json
{
    "name": "Imobiliária | Cadastros",
    "goal": "lead_generation",
    "budget": {
        "amount_cents": 6000
    },
    "special_ad_category": "HOUSING",
    "targeting": {
        "countries": [
            "BR"
        ]
    },
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Apartamentos na planta",
            "body": "Cadastre-se e receba a tabela de preços.",
            "call_to_action": "SIGN_UP"
        }
    ],
    "lead_form": {
        "name": "Tabela de preços",
        "fields": [
            "FULL_NAME",
            "EMAIL",
            "PHONE"
        ],
        "questions": [
            {
                "question": "Quantos quartos você procura?",
                "options": [
                    "1",
                    "2",
                    "3 ou mais"
                ]
            }
        ],
        "privacy_policy_url": "https://imobiliaria.exemplo.com.br/privacidade",
        "thank_you": {
            "title": "Recebemos seu cadastro!",
            "body": "Um corretor fala com você em até 1 hora útil."
        }
    }
}
```

### Ciclo de vida da campanha

| Status | Significa |
| --- | --- |
| creating | Na fila de publicação (o LeadGo está enviando à Meta). |
| failed | A publicação falhou; o motivo vem em error.message. Corrija e use POST /campaigns/{id}/retry. |
| pending_review | Na Meta, em análise dos anúncios. |
| active | Rodando. |
| paused | Pausada (por você, pela API, pelo Gestor IA ou na Meta). |
| rejected | Reprovada pela Meta; veja rejection_reason nos anúncios. |
| error | Com problema na Meta (pagamento, política...). |
| completed | Terminou (data de término). |
| cancelled | Excluída ou arquivada direto na Meta. |

Cada mudança de status ou de verba (pela API, pelo painel ou na Meta) abre a janela de maturação do Gestor IA: por alguns dias ele só sugere, sem mexer na campanha.

## Opções avançadas de entrega

Remarketing, exclusões, raio, idiomas, posicionamentos, UTMs e estratégia de lance.

### Remarketing e exclusões

Para anunciar de novo para quem já visitou o site ou interagiu com a Página, crie o público em POST /audiences (type website com o Pixel, ou meta_engagement) e use o platform_audience_id em targeting.audience_id. Para deixar alguém de fora (quem já comprou, por exemplo), use targeting.excluded_audience_ids. Os públicos que já existem na Meta entram com POST /audiences/sync.

- Um público novo leva algumas horas para encher, e a Meta só entrega para ele a partir de cerca de 100 pessoas.
- Visitantes do site ficam até 180 dias; engajamento, até 365.
- Excluir cidades: targeting.excluded_cities, com as mesmas cidades de GET /targeting/cities.

POST /campaigns: Remarketing sem quem já comprou:

```json
{
    "name": "Remarketing | Visitantes 30 dias",
    "goal": "traffic",
    "budget": {
        "amount_cents": 3000
    },
    "targeting": {
        "cities": [
            {
                "key": "269969",
                "name": "São Paulo, São Paulo",
                "country_code": "BR",
                "radius_km": 25
            }
        ],
        "audience_id": "23859876543210987",
        "excluded_audience_ids": [
            "23851234567890123"
        ],
        "languages": [
            "pt"
        ]
    },
    "link_url": "https://loja.exemplo.com.br/outubro",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Ainda pensando?",
            "body": "As peças que você viu estão com 10% off até domingo.",
            "call_to_action": "SHOP_NOW"
        }
    ]
}
```

### Raio, idiomas, grupos de interesses e Advantage+

- Raio: cities[].radius_km em 17, 25, 40, 50, 80 km em volta da cidade.
- Idiomas: targeting.languages (pt, es, en); sem idioma, todos.
- targeting.advantage_audience true deixa a Meta ampliar o público quando achar gente com mais chance de resultado.
- Grupos de interesses: em targeting.interests basta um dos interesses (OU); cada grupo de targeting.narrow_interests (até 3) também precisa ser atendido, com um interesse dele (E). Não combina com Advantage+ público. O público encolhe rápido: confira a estimativa antes.
- Antes de criar, POST /targeting/reach-estimate diz quantas pessoas cabem no público. Com grupos, narrowed vem true, upper é o teto do menor grupo e warning avisa quando o público pode não bastar para a Meta entregar.

POST /campaigns: Público com interesses em E:

```json
{
    "name": "Lançamento | Lotes na praia",
    "goal": "traffic",
    "budget": {
        "amount_cents": 8000
    },
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 35,
        "interests": [
            {
                "id": "6003012345678",
                "name": "Investimento imobiliário"
            }
        ],
        "narrow_interests": [
            [
                {
                    "id": "6003023456789",
                    "name": "Condomínio fechado"
                },
                {
                    "id": "6003034567890",
                    "name": "Casa de praia"
                }
            ]
        ]
    },
    "link_url": "https://imobiliaria.exemplo.com.br/lancamento",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Lotes a 300 m da praia",
            "body": "Condomínio fechado com marina. Veja as condições de lançamento.",
            "call_to_action": "LEARN_MORE"
        }
    ]
}
```

### Posicionamentos

Sem placements, a Meta escolhe onde mostrar (automático, o recomendado). Para escolher, envie a lista:

| Valor | Onde aparece |
| --- | --- |
| facebook_feed | Feed do Facebook |
| facebook_stories | Stories do Facebook |
| facebook_reels | Reels do Facebook |
| facebook_video_feeds | Vídeos do Facebook |
| facebook_marketplace | Marketplace |
| instagram_feed | Feed do Instagram |
| instagram_stories | Stories do Instagram |
| instagram_reels | Reels do Instagram |
| instagram_explore | Explorar do Instagram |
| messenger_inbox | Caixa de entrada do Messenger |
| audience_network | Audience Network (apps e sites parceiros) |

### UTMs, eventos do site e perfil do Instagram

Todo anúncio novo, de qualquer objetivo (inclusive cadastro, WhatsApp e post impulsionado), sai com os 4 UTMs padrão abaixo, sempre: não há como tirá-los. tracking.url_tags é a lista de extras, somados a eles em todos os anúncios, com as macros da Meta. Um extra com a chave de um dos 4 troca o valor dele, na mesma posição (ex.: utm_campaign=blackfriday); os demais vêm depois, na ordem enviada. Ausente ou [], a campanha sai só com os 4. Até a 1.1.0, cadastro e WhatsApp saíam sem UTMs.

Os 4 UTMs padrão (sempre incluídos):

```json
[
    {
        "key": "utm_source",
        "value": "facebook"
    },
    {
        "key": "utm_medium",
        "value": "paid"
    },
    {
        "key": "utm_campaign",
        "value": "{{campaign.name}}"
    },
    {
        "key": "utm_content",
        "value": "{{ad.name}}"
    }
]
```

Nas respostas (o draft da campanha, o do padrão e o validate_only), tracking.url_tags mostra a lista efetiva: os 4, com o valor trocado quando houver, e depois os extras. Reenviar essa lista (num padrão, numa duplicação) dá o mesmo resultado, sem chave repetida: o limite de 10 extras conta só as chaves fora das 4. Numa campanha publicada antes da 1.1.0 sem UTMs, aparecem os 4, que uma cópia (duplicar ou salvar como padrão) usaria: os anúncios dela seguem como foram publicados. Depois de publicado, cada anúncio troca as UTMs em PATCH /ads/{id}.

Enviado: dois extras:

```json
{
    "tracking": {
        "url_tags": [
            {
                "key": "utm_campaign",
                "value": "blackfriday"
            },
            {
                "key": "utm_term",
                "value": "promo"
            }
        ]
    }
}
```

Na resposta: a lista efetiva:

```json
{
    "tracking": {
        "url_tags": [
            {
                "key": "utm_source",
                "value": "facebook"
            },
            {
                "key": "utm_medium",
                "value": "paid"
            },
            {
                "key": "utm_campaign",
                "value": "blackfriday"
            },
            {
                "key": "utm_content",
                "value": "{{ad.name}}"
            },
            {
                "key": "utm_term",
                "value": "promo"
            }
        ]
    }
}
```

Todo anúncio novo, em qualquer objetivo (inclusive o post impulsionado), sai com "Eventos do site" marcado, e o Pixel é obrigatório. Ele é escolhido nesta ordem: tracking.pixel_id, que vale em qualquer objetivo (nas conversões, é também o Pixel que elas otimizam); o Pixel padrão da conta (default_pixel_id em GET /ad-account, definido em Leads › Configurações); e, sem padrão, o único Pixel que a conta enxerga (GET /ad-account/pixels), que passa a ser o padrão dela. Sem Pixel para escolher, a criação é recusada com 422 em tracking.pixel_id, e nada é criado:

- A conta enxerga mais de um Pixel e não tem padrão: envie tracking.pixel_id ou defina o padrão no painel.
- A conta não tem Pixel: crie um no Gerenciador de Eventos da Meta.
- A consulta dos Pixels falhou: tente de novo em instantes (não quer dizer que a conta não tem Pixel).

Vale para POST /campaigns, POST /campaigns/preview, POST /templates/{id}/campaigns, POST /campaigns/{id}/duplicate e POST /boosts (que também aceita tracking.pixel_id), inclusive com ?validate_only=true. Um padrão de outra conta de anúncio não leva o Pixel dele: vale o da conta vinculada. Editar um padrão (PATCH /templates/{id}) não exige o Pixel, fora das conversões. Uma campanha com falha publicada antes da 1.2.0 ganha o Pixel ao ser reenviada (POST /campaigns/{id}/retry); sem Pixel para escolher, o reenvio é recusado com operation_failed. A resposta (o draft da campanha, o boost e o validate_only) mostra o Pixel escolhido em tracking.pixel_id.

instagram_account_id escolhe o perfil que assina o anúncio no Instagram (GET /ad-account/instagram-accounts); sem ele, vale o perfil ligado à Página.

### Estratégia de lance

Sem bidding, o lance é automático (menor custo), o melhor ponto de partida. Com histórico, dá para segurar o custo:

| bidding.strategy | Como funciona |
| --- | --- |
| COST_CAP | Custo por resultado (meta de custo): A Meta tenta manter o custo médio de cada resultado perto do valor informado. |
| LOWEST_COST_WITH_BID_CAP | Lance máximo por leilão: A Meta nunca oferece mais que o valor informado em cada leilão. |
| LOWEST_COST_WITH_MIN_ROAS | ROAS mínimo: A Meta busca vendas que devolvam pelo menos o retorno informado (só em vendas com valor). |

POST /campaigns: Vendas com custo por resultado:

```json
{
    "name": "Vendas | Custo por compra R$ 40",
    "goal": "conversions",
    "budget": {
        "amount_cents": 20000
    },
    "tracking": {
        "pixel_id": "123456789012345",
        "conversion_event": "PURCHASE"
    },
    "bidding": {
        "strategy": "COST_CAP",
        "amount_cents": 4000
    },
    "placements": [
        "instagram_feed",
        "instagram_stories"
    ],
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 25,
        "age_max": 54
    },
    "link_url": "https://loja.exemplo.com.br/mais-vendidos",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Os mais vendidos",
            "body": "Frete grátis acima de R$ 199.",
            "call_to_action": "SHOP_NOW"
        }
    ]
}
```

## Erros

O formato único dos erros e todos os códigos que a API responde.

### Formato e códigos

Erros vêm com o status HTTP certo e um corpo de formato único. Trate pelo code, que é estável; message pode mudar de texto. Na validação (422), errors traz as mensagens por campo.

422 Unprocessable Content:

```json
{
    "message": "O orçamento diário mínimo desta conta é R$ 6,00 (600 centavos).",
    "code": "validation_failed",
    "errors": {
        "budget.amount_cents": [
            "O orçamento diário mínimo desta conta é R$ 6,00 (600 centavos)."
        ]
    },
    "request_id": "req_01jbz7m3q7t1x5b9f3j7n1r5v9"
}
```

| code | HTTP | Quando |
| --- | --- | --- |
| token_invalid | 401 | Sem o cabeçalho Authorization ou com um token que não existe. |
| token_revoked | 401 | O token foi revogado no painel. |
| token_expired | 401 | O token passou da validade. Gere outro no painel. |
| tenant_suspended | 403 | A conta (ou a agência que a gerencia) está suspensa. |
| subscription_inactive | 402 | A conta não tem um plano ativo. |
| payment_overdue | 402 | Há uma fatura vencida além da carência. |
| token_owner_lost_access | 403 | Quem criou o token saiu da conta ou perdeu o acesso a ela. |
| missing_ability | 403 | O token não tem o escopo exigido pela rota (veja details.ability). |
| permission_denied | 403 | Quem criou o token não tem a permissão equivalente no painel. |
| not_found | 404 | O recurso não existe nesta conta. |
| validation_failed | 422 | Campos inválidos; o detalhe de cada um vem em errors. |
| rate_limited | 429 | Passou de 120 requisições por minuto. Espere o Retry-After. |
| platform_quota_exceeded | 429 | Muitas consultas novas à plataforma (buscas e métricas fora do cache). |
| ad_account_not_linked | 409 | A conta ainda não vinculou uma conta de anúncio da Meta. |
| campaign_not_published | 409 | A campanha ainda não está na plataforma. |
| campaign_not_toggleable | 409 | O status atual não permite ligar nem pausar. |
| not_toggleable | 409 | O status atual do conjunto ou do anúncio não permite ligar nem pausar. |
| ad_not_published | 409 | O anúncio ainda não está na plataforma. |
| ad_media_not_supported | 422 | Anúncio de vídeo: pela API, troque só texto, link, botão e UTMs. |
| boost_creative_locked | 422 | Anúncio de post impulsionado: o criativo é o próprio post e não muda pela API. |
| preview_unavailable | 422 | A prévia da Meta vale para anúncios com imagem e link (não para vídeo nem WhatsApp). |
| campaign_publishing | 409 | A campanha está sendo publicada; aguarde terminar. |
| campaign_not_duplicable | 409 | Só campanhas criadas pelo LeadGo podem ser duplicadas. |
| ad_account_changed | 409 | A campanha é de uma conta de anúncio que não está mais vinculada. |
| too_many_publishing | 429 | Já há 5 campanhas sendo publicadas nesta conta. |
| campaign_creation_limit | 429 | Passou de 30 campanhas criadas por hora. |
| idempotency_key_invalid | 400 | A Idempotency-Key tem mais de 255 caracteres. |
| idempotency_key_reused | 422 | A Idempotency-Key já foi usada com outro corpo. |
| idempotency_in_progress | 409 | O pedido com esta Idempotency-Key ainda está em andamento. |
| media_rejected | 422 | O arquivo não é JPG, PNG, MP4 ou MOV, ou passa do tamanho. |
| too_many_imports | 429 | Já há 5 mídias baixando por URL nesta conta. |
| sync_too_soon | 429 | Uma sincronização foi pedida há pouco (campanhas: 5 minutos; públicos: 1 minuto). Espere o Retry-After. |
| campaign_not_templatable | 409 | Posts impulsionados, campanhas ainda publicando e importadas com um objetivo que o LeadGo não recria não viram padrão. |
| brain_disabled | 409 | O Gestor IA está desligado nas configurações da conta. |
| analysis_in_progress | 409 | Já há uma análise em andamento nesta conta; o relatório sai em instantes. |
| analysis_too_soon | 429 | Já houve uma análise pedida nesta conta na última hora. |
| report_not_retryable | 409 | Só a análise mais recente da conta, quando falhou, pode ser refeita. |
| report_not_decidable | 409 | O relatório não aceita mais decisões (já decidido, expirado ou substituído). |
| action_not_decidable | 409 | A ação já foi decidida ou é só uma sugestão. |
| rollback_unavailable | 409 | A ação não pode ser desfeita; o motivo vem em message. |
| audience_not_deletable | 409 | Público do sistema (compradores) ou criado direto na Meta: não se exclui pela API. |
| ai_rate_limited | 429 | Limite de rascunhos pela IA por hora (somado ao painel). |
| ai_unavailable | 503 | A IA não respondeu; tente de novo em instantes. |
| webhook_limit_reached | 422 | A conta já tem 5 webhooks. |
| operation_failed | 422 | Uma regra do painel impediu a operação; o motivo vem em message. |
| platform_rejected | 422 | A Meta (via Zernio) recusou o pedido; o motivo vem em message. |
| platform_unavailable | 502 | A plataforma de anúncios não respondeu. Tente de novo. |
| platform_rate_limited | 503 | A plataforma de anúncios pediu uma pausa. Espere o Retry-After. |
| server_error | 500 | Erro do nosso lado. Informe o request_id ao suporte. |

## Limites, idempotência e paginação

Quantas chamadas cabem por minuto, como repetir sem duplicar e como paginar listas.

### Limites

- 120 requisições por minuto por conta (todos os tokens somam). Os cabeçalhos X-RateLimit-Limit e X-RateLimit-Remaining mostram o saldo; ao passar, 429 com Retry-After.
- Consultas novas à plataforma (buscas de cidade e interesse, métricas fora do cache): 10 por minuto por conta. Repetidas vêm do cache e não contam.
- Até 5 campanhas publicando ao mesmo tempo e 30 criadas por hora por conta. A Meta bloqueia contas de anúncio que criam demais em pouco tempo.
- Rascunho pela IA: o mesmo limite por hora do painel, por pessoa.
- Sincronização manual: uma a cada 5 minutos.

### Idempotência

Nas rotas que criam (POST /campaigns, /duplicate, /retry e /templates/{id}/campaigns), envie Idempotency-Key com um valor único por operação (um UUID). Repetir o mesmo pedido com a mesma chave, em até 24 h, devolve a primeira resposta com o cabeçalho Idempotent-Replayed: true, sem criar de novo.

- Mesma chave com outro corpo: 422 idempotency_key_reused.
- Enquanto o primeiro pedido ainda roda: 409 idempotency_in_progress (tente de novo em segundos).
- Erros 5xx, 409 e 429 não ficam guardados: repetir com a mesma chave executa de novo.
- Cada token tem as suas chaves.

### Paginação

Listas longas (campanhas e mídias) são paginadas por cursor: siga links.next até ele vir null, ou envie ?cursor= com meta.next_cursor. Em /campaigns, limit vai de 1 a 100 (padrão 25).

## Recebendo webhooks

Os avisos que o seu sistema recebe, o corpo de cada um e como conferir a assinatura.

### Eventos

Cadastre a URL do seu sistema em POST /webhooks (ou no painel, em API) e receba um POST em JSON a cada evento. Responda 2xx em até 15 segundos; o processamento pesado deve ficar para depois da resposta.

| Evento | Quando | Em data |
| --- | --- | --- |
| campaign.published | A campanha chegou à plataforma (em análise, ativa ou pausada). | {campaign, previous_status} |
| campaign.failed | A publicação falhou; o motivo vem em error.message. Corrija e use POST /campaigns/{id}/retry. | {campaign, previous_status} |
| campaign.status_changed | O status mudou depois de publicada (aprovada, pausada, reprovada, concluída, excluída na Meta...). | {campaign, previous_status} |
| campaign.deleted | A campanha foi excluída (pela API, pelo painel ou descartada). | {campaign} |
| ad.approved | Um anúncio passou na revisão da Meta (ou teve a reprovação revertida). | {campaign_id, ad} |
| ad.rejected | Um anúncio foi reprovado pela Meta; o motivo vem em rejection_reason. | {campaign_id, ad} |
| alert.created | Algo novo pede atenção na conta de anúncio: pagamento, problema de entrega, verba no limite ou gasto sem resultado. | {alert} |
| ad_account.status_changed | A situação da conta de anúncio mudou (ativa, restrita, desativada ou encerrada). | {ad_account, previous_status, status} |
| lead.created | Chegou um lead novo de um formulário instantâneo. | {lead} |
| lead.status_changed | O status de um lead mudou (contatado, qualificado, venda, perdido...), pela API ou pelo painel. | {lead, previous_status} |
| lead.scored | A IA deu a nota do lead (quente, morno ou frio) com o motivo e a mensagem sugerida. | {lead} |
| brain.report_ready | O Gestor IA terminou uma análise: aguardando aprovação, sem ação ou já executada no automático. | {report} (com actions) |
| media.ready | A mídia enviada por URL foi baixada e está pronta para os criativos. | {media} |
| media.failed | Não deu para baixar a mídia enviada por URL; o motivo vem em error. | {media} |
| webhook.test | Envio de teste (botão "Testar" ou POST /webhooks/{id}/test). | {message} |

Cada objeto em data vem no mesmo formato da rota GET correspondente (campaign como GET /campaigns/{id} sem os conjuntos, lead como GET /leads/{id} sem o histórico). Os eventos lead.* exigem que quem cadastra o webhook possa ver os leads, e brain.report_ready, que possa ver o Gestor IA.

Corpo de campaign.published:

```json
{
    "id": "evt_01jbz9a1c5f9j3m7q1t5x9b3e7",
    "type": "campaign.published",
    "api_version": "v1",
    "created_at": "2026-10-28T15:02:11+00:00",
    "account_id": "01jbw8r5x9a3d7g1k5n9s3w7b1",
    "data": {
        "campaign": {
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Promoção de outubro",
            "goal": "traffic",
            "goal_label": "Tráfego",
            "status": "pending_review",
            "status_label": "Em análise",
            "created_via": "api",
            "budget": {
                "level": "campaign",
                "type": "daily",
                "amount_cents": 5000,
                "currency": "BRL"
            },
            "schedule": {
                "starts_at": "2026-10-28T03:00:00+00:00",
                "ends_at": null
            },
            "error": null,
            "platform": "meta",
            "platform_campaign_id": "120212345678900001",
            "published_at": "2026-10-28T15:02:11+00:00",
            "synced_at": "2026-10-28T15:40:00+00:00",
            "created_at": "2026-10-28T15:00:00+00:00",
            "updated_at": "2026-10-28T15:02:11+00:00",
            "links": {
                "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
            }
        },
        "previous_status": "creating"
    }
}
```

### Conferindo a assinatura

Cada aviso traz X-Leadgo-Event, X-Leadgo-Event-Id (o mesmo em todas as tentativas), X-Leadgo-Timestamp e X-Leadgo-Signature. A assinatura é v1= seguido do HMAC-SHA256, em hexadecimal, de "{timestamp}.{corpo}" com o segredo do webhook. No seu receptor:

- Leia o corpo cru, exatamente como chegou, antes de converter o JSON.
- Calcule v1= + HMAC-SHA256 (hex) de "{X-Leadgo-Timestamp}.{corpo}" com o segredo do webhook.
- Compare com X-Leadgo-Signature em tempo constante (hash_equals, crypto.timingSafeEqual, hmac.compare_digest).
- Recuse avisos com mais de 5 minutos de diferença entre o timestamp e o seu relógio.
- Descarte eventos repetidos pelo X-Leadgo-Event-Id.

Conferência no terminal (openssl):

```bash
# corpo.json: o corpo cru do aviso, byte a byte.
# TIMESTAMP e ASSINATURA: os cabeçalhos X-Leadgo-Timestamp e X-Leadgo-Signature.
ESPERADA="v1=$( { printf '%s.' "$TIMESTAMP"; cat corpo.json; } \
  | openssl dgst -sha256 -hmac "$LEADGO_WEBHOOK_SECRET" | sed 's/^.* //')"

[ "$ESPERADA" = "$ASSINATURA" ] && echo "assinatura válida" || echo "recusar"
```

Para testar o receptor, peça um aviso webhook.test assinado:

POST /webhooks/{id}/test:

```bash
curl -X POST "https://leadgo.com.br/api/v1/webhooks/01jbz8e2h6k0n4r8u2x6b0f4jm/test" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

### Entregas e novas tentativas

- Sem resposta 2xx, tentamos de novo depois de 1, 5, 15 e 60 minutos.
- Depois de 10 avisos perdidos seguidos, o webhook é desligado e quem administra a API é avisado no painel.
- Os avisos podem chegar fora de ordem: o corpo é uma foto do momento (veja previous_status). Na dúvida, consulte GET /campaigns/{id}.
- O endereço precisa ser https público; não seguimos redirecionamentos.
- Até 5 webhooks por conta.

## Leads no seu CRM

Receber cada lead na hora, devolver a venda e fechar o ciclo com a Meta.

### Receber cada lead na hora

Cadastre um webhook com lead.created, lead.scored e lead.status_changed. O lead chega com nome, e-mail, telefone, as respostas do formulário e a campanha de origem; a nota da IA (quente, morno ou frio), com a mensagem sugerida para o primeiro contato, chega logo depois em lead.scored.

POST /webhooks:

```bash
curl -X POST "https://leadgo.com.br/api/v1/webhooks" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://crm.exemplo.com.br/hooks/leadgo",
    "events": [
        "lead.created",
        "lead.scored",
        "lead.status_changed"
    ]
}'
```

Na primeira carga, ou para conferir se algum ficou para trás, liste com GET /leads?received_from=AAAA-MM-DD e pagine pelo cursor. Grave o id do lead no seu CRM para as próximas chamadas.

### Devolver o resultado

Quando o vendedor avançar o lead no seu CRM, avise a API. Nos planos com inteligência de leads, a qualificação e a venda vão para a Meta, que passa a buscar gente parecida com quem compra, e os compradores entram no público semelhante.

| No seu CRM | Chamada |
| --- | --- |
| Primeiro contato | POST /leads/{id}/contacted |
| Lead bom, em negociação | PATCH /leads/{id} com status qualified |
| Venda fechada | PATCH /leads/{id} com status won e value_cents |
| Não fechou | PATCH /leads/{id} com status lost e lost_reason |
| Contato falso ou repetido | PATCH /leads/{id} com status spam |
| Anotação do vendedor | POST /leads/{id}/notes |

PATCH /leads/{id}:

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/leads/01jbz2k8m4q0t6w2y8b4e0h6na" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "won",
    "value_cents": 25900
}'
```

Mudanças feitas no painel também chegam em lead.status_changed: use o previous_status para não devolver ao CRM o que veio dele.

### Dados pessoais (LGPD)

- Tokens com leads:read leem dados pessoais: dê esse escopo só às integrações que precisam.
- Pedido do titular para apagar: DELETE /leads/{id} apaga o lead e o histórico aqui (na Meta, os leads expiram em 90 dias).
- GET /leads/stats traz os números do funil sem dado pessoal, para painéis e relatórios.

## Implementar com IA

Um arquivo com a documentação inteira para colar na sua IA e ela escrever a integração.

### O arquivo para IA

Toda esta documentação também existe num único arquivo Markdown, escrito para IAs de programação (ChatGPT, Claude, Gemini, Cursor, Copilot). Ele traz as regras da integração, um roteiro de implementação, todas as rotas com campos, exemplos em cURL e respostas, os erros e os webhooks. Baixe ou copie pelos botões do topo da página, ou use o endereço abaixo.

Endereço do arquivo:

```text
https://leadgo.com.br/docs/api.md
```

### Como usar

- Cole o arquivo inteiro na conversa (ou anexe-o) e diga o que o seu sistema precisa fazer.
- Informe a linguagem e o framework do seu sistema e onde ficam as variáveis de ambiente.
- Peça para a IA seguir as "Instruções para a IA" e o "Roteiro de implementação" do arquivo.
- Teste com um token de uma conta de teste e com ?validate_only=true antes de criar campanhas de verdade.

Pedido sugerido:

```text
Implemente a integração do meu sistema com a API do LeadGo seguindo o documento em anexo.
Meu sistema: [linguagem e framework].
Quero: [ex.: criar uma campanha para cada produto novo e receber os leads no CRM].
O token fica em LEADGO_TOKEN e o segredo do webhook em LEADGO_WEBHOOK_SECRET.
Siga as "Instruções para a IA" e o "Roteiro de implementação" do documento e escreva testes.
```

Prefere gerar um cliente automaticamente? A especificação OpenAPI 3.1 tem as mesmas rotas: https://leadgo.com.br/docs/api/openapi.json.

## Versões e mudanças

Como a API evolui sem quebrar a sua integração.

### Versões

A v1 é estável. Novos campos e rotas podem aparecer sem aviso: ignore o que não conhece. Evitamos mudanças que quebram integração na v1; quando uma é inevitável, ela aparece em destaque na lista abaixo, com a data.

- 1.2.0 (30/10/2026): "Eventos do site" em todo anúncio novo, com o Pixel obrigatório: tracking.pixel_id passa a valer em qualquer objetivo (antes, só nas conversões) e, sem ele, vale o Pixel padrão da conta ou o único que ela enxerga, que vira o padrão. Três mudanças de comportamento: sem Pixel para escolher (a conta tem vários e nenhum padrão, não tem nenhum, ou a consulta dos Pixels falhou), POST /campaigns, POST /campaigns/preview, POST /templates/{id}/campaigns, POST /campaigns/{id}/duplicate e POST /boosts passam a recusar com 422 em tracking.pixel_id, também com ?validate_only=true (antes, o anúncio saía sem "Eventos do site"), e POST /campaigns/{id}/retry, com operation_failed; o draft, o boost (que passa a aceitar tracking.pixel_id) e o validate_only passam a mostrar em tracking.pixel_id o Pixel escolhido, em todo objetivo; e os 4 UTMs padrão, com os extras de tracking.url_tags, passam a ir também nos anúncios de WhatsApp, de cadastro e de post impulsionado (antes, só nos com link). Anúncios já publicados não mudam.
- 1.1.0 (29/10/2026): arte, vídeo e carrossel na mesma campanha (creatives[].cards; o formato antigo segue aceito); padrões e duplicação com carrossel; prévia de qualquer anúncio da campanha; "Eventos do site" e os 4 UTMs padrão em todo anúncio novo com link (anúncios já publicados não mudam); PATCH /templates/{id} edita o padrão por completo, menos o objetivo e a conta de anúncio (os campos de POST /templates/{id}/campaigns e duration_days; a mídia pode faltar, e um media_id vira mídia guardada; com ?validate_only=true, só confere). Cinco mudanças de comportamento: tracking.url_tags passa a ser a lista de extras, somados aos 4 UTMs padrão, que vão sempre (um extra com a chave de um deles troca o valor dele; antes, iam só os enviados); em POST /templates/{id}/campaigns com creatives, só saem os anúncios enviados ({} mantém um do padrão); em POST /templates/{id}/campaigns de um padrão com persona, as exclusões, os idiomas e o Advantage+ guardados no padrão passam a valer junto do público dela (o Advantage+ sai sozinho quando a persona tem grupos de interesses; antes, só o público da conta), e persona_id null usa o público guardado no padrão (antes, o Brasil, de 18 a 65 anos); em POST /campaigns/{id}/duplicate, media_id ou template_media_index num criativo ou num cartão passa a ser recusado (antes era ignorado); em POST /campaigns e POST /campaigns/preview, o título, o texto, a descrição e o botão que faltarem num criativo depois do primeiro passam a vir do primeiro (antes, a descrição ficava vazia e o botão era LEARN_MORE). Para não herdar, envie o campo no próprio item: "description": null deixa o anúncio sem descrição e "call_to_action": null volta ao botão LEARN_MORE.
- 1.0.0 (28/10/2026): primeira versão pública. Campanhas (com carrossel, variações de texto, prévia e impulsionar post), conjuntos e anúncios, mídias, personas e padrões, públicos de remarketing, métricas e relatórios, leads, Gestor IA, saúde e alertas da conta, rascunho pela IA e webhooks.

Especificação OpenAPI 3.1 para importar no Postman ou no Insomnia, ou para gerar um SDK: https://leadgo.com.br/docs/api/openapi.json.

## Mapa das rotas

| Método | Caminho | Escopo | O que faz |
| --- | --- | --- | --- |
| GET | /me | — | Conferir o token |
| GET | /ad-account | campaigns:read | Conta de anúncio vinculada |
| GET | /ad-account/pixels | campaigns:read | Pixels da conta |
| GET | /ad-account/lead-forms | campaigns:read | Formulários de cadastro |
| GET | /ad-account/instagram-accounts | campaigns:read | Perfis do Instagram |
| GET | /ad-account/instagram-posts | campaigns:read | Posts do Instagram |
| GET | /ad-account/health | campaigns:read | Saúde da conta de anúncio |
| GET | /ad-account/finance | campaigns:read | Saldo e limite de gasto |
| PATCH | /ad-account | account:manage | Alterar o limite de gasto |
| GET | /alerts | campaigns:read | Alertas da conta |
| POST | /alerts/{id}/dismiss | account:manage | Dispensar alerta |
| POST | /ad-account/sync | campaigns:read | Sincronizar com a Meta |
| GET | /reference | campaigns:read | Valores aceitos |
| GET | /targeting/cities | campaigns:read | Buscar cidades |
| GET | /targeting/interests | campaigns:read | Buscar interesses |
| POST | /targeting/reach-estimate | campaigns:read | Estimar alcance |
| POST | /media | media:write | Enviar mídia |
| GET | /media | media:write | Listar mídias |
| GET | /media/{id} | media:write | Consultar mídia |
| DELETE | /media/{id} | media:write | Excluir mídia |
| GET | /campaigns | campaigns:read | Listar campanhas |
| POST | /campaigns | campaigns:write | Criar campanha |
| POST | /campaigns/preview | campaigns:write | Prévia antes de criar |
| POST | /boosts | campaigns:write | Impulsionar post |
| GET | /campaigns/{id} | campaigns:read | Consultar campanha |
| PATCH | /campaigns/{id} | campaigns:write | Alterar campanha |
| DELETE | /campaigns/{id} | campaigns:write | Excluir campanha |
| POST | /campaigns/{id}/retry | campaigns:write | Reenviar publicação |
| POST | /campaigns/{id}/duplicate | campaigns:write | Duplicar campanha |
| POST | /templates/{id}/campaigns | campaigns:write | Criar campanha a partir de um padrão |
| PATCH | /ad-sets/{id} | campaigns:write | Alterar conjunto |
| DELETE | /ad-sets/{id} | campaigns:write | Excluir conjunto |
| PATCH | /ads/{id} | campaigns:write | Alterar anúncio |
| DELETE | /ads/{id} | campaigns:write | Excluir anúncio |
| GET | /ads/{id}/preview | campaigns:read | Prévia do anúncio |
| GET | /personas | campaigns:read | Listar personas |
| GET | /personas/{id} | campaigns:read | Consultar persona |
| POST | /personas | campaigns:write | Criar persona |
| PATCH | /personas/{id} | campaigns:write | Alterar persona |
| DELETE | /personas/{id} | campaigns:write | Excluir persona |
| POST | /personas/generate | ai:draft | Sugerir persona pela IA |
| GET | /templates | campaigns:read | Listar padrões de campanha |
| GET | /templates/{id} | campaigns:read | Consultar padrão |
| POST | /campaigns/{id}/template | campaigns:write | Criar padrão a partir de uma campanha |
| PATCH | /templates/{id} | campaigns:write | Alterar padrão |
| DELETE | /templates/{id} | campaigns:write | Excluir padrão |
| GET | /audiences | campaigns:read | Listar públicos |
| GET | /audiences/{id} | campaigns:read | Consultar público |
| POST | /audiences | campaigns:write | Criar público de remarketing |
| DELETE | /audiences/{id} | campaigns:write | Excluir público |
| POST | /audiences/sync | campaigns:write | Sincronizar públicos com a Meta |
| GET | /campaigns/{id}/metrics | reports:read | Métricas da campanha |
| GET | /ads/{id}/metrics | reports:read | Métricas do anúncio |
| GET | /reports/account | reports:read | Relatório da conta |
| GET | /reports/campaigns | reports:read | Resultado por campanha |
| GET | /leads | leads:read | Listar leads |
| GET | /leads/stats | leads:read | Indicadores do funil |
| GET | /leads/{id} | leads:read | Consultar lead |
| PATCH | /leads/{id} | leads:write | Mudar a etapa do lead |
| POST | /leads/{id}/contacted | leads:write | Registrar contato |
| POST | /leads/{id}/notes | leads:write | Adicionar nota |
| DELETE | /leads/{id} | leads:write | Excluir lead (LGPD) |
| GET | /brain/reports | brain:read | Listar relatórios do Gestor IA |
| GET | /brain/reports/{id} | brain:read | Consultar relatório |
| POST | /brain/reports | brain:manage | Pedir uma análise agora |
| POST | /brain/reports/{id}/retry | brain:manage | Refazer análise que falhou |
| POST | /brain/reports/{id}/approve | brain:manage | Aprovar todas as ações |
| POST | /brain/reports/{id}/reject | brain:manage | Recusar todas as ações |
| POST | /brain/actions/{id}/approve | brain:manage | Aprovar uma ação |
| POST | /brain/actions/{id}/reject | brain:manage | Recusar uma ação |
| POST | /brain/actions/{id}/rollback | brain:manage | Desfazer uma ação aplicada |
| POST | /campaign-briefs | ai:draft | Montar campanha com a IA |
| GET | /webhooks | webhooks:manage | Listar webhooks |
| POST | /webhooks | webhooks:manage | Criar webhook |
| GET | /webhooks/{id} | webhooks:manage | Consultar webhook |
| PATCH | /webhooks/{id} | webhooks:manage | Alterar webhook |
| DELETE | /webhooks/{id} | webhooks:manage | Excluir webhook |
| POST | /webhooks/{id}/test | webhooks:manage | Testar webhook |

## Referência das rotas

### Conta e conta de anúncio

Quem é o token, a conta de anúncio vinculada, o que ela oferece às campanhas (Pixels, formulários, perfis do Instagram), a saúde, o saldo e os alertas.

#### Conferir o token

`GET /me`

A conta que o token opera, quem o criou e os escopos. Bom primeiro teste da integração.

```bash
curl -X GET "https://leadgo.com.br/api/v1/me" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "token": {
            "id": "01jbw9t3b7e1h5m9q3v7z1d5gk",
            "name": "Loja virtual",
            "prefix": "lgo_x7Kp2mQa",
            "abilities": [
                "campaigns:read",
                "campaigns:write",
                "media:write",
                "reports:read",
                "ai:draft",
                "webhooks:manage",
                "account:manage",
                "leads:read",
                "leads:write",
                "brain:read",
                "brain:manage"
            ],
            "expires_at": "2027-10-28T15:00:00+00:00",
            "created_at": "2026-10-28T15:00:00+00:00"
        },
        "account": {
            "id": "01jbw8r5x9a3d7g1k5n9s3w7b1",
            "name": "Loja Exemplo",
            "timezone": "America/Sao_Paulo",
            "is_managed_client": false
        },
        "user": {
            "id": 42,
            "name": "Ana Souza",
            "email": "ana@loja.exemplo.com.br"
        },
        "rate_limit": {
            "requests_per_minute": 120
        }
    }
}
```

Erros: `token_invalid`, `token_revoked`, `token_expired`, `subscription_inactive`, `payment_overdue`.

#### Conta de anúncio vinculada

`GET /ad-account` · escopo `campaigns:read`

Onde as campanhas são criadas: moeda, fuso, verba diária mínima, Pixel padrão e a Página do Facebook da conexão. Cada conta opera uma única conta de anúncio, escolhida em Integrações no painel.

```bash
curl -X GET "https://leadgo.com.br/api/v1/ad-account" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": 12,
        "name": "Loja Exemplo · Meta",
        "platform": "meta",
        "platform_ad_account_id": "act_1234567890",
        "currency": "BRL",
        "timezone": "America/Sao_Paulo",
        "minimum_daily_budget_cents": 600,
        "default_pixel_id": "123456789012345",
        "payment_method_missing": false,
        "page": {
            "id": "998877665544",
            "name": "Loja Exemplo"
        },
        "connection_status": "connected",
        "last_synced_at": "2026-10-28T14:40:00+00:00"
    }
}
```

Erros: `ad_account_not_linked`.

#### Pixels da conta

`GET /ad-account/pixels` · escopo `campaigns:read`

Pixels (conjuntos de dados) que a conta de anúncio enxerga, para tracking.pixel_id: o Pixel que marca "Eventos do site" nos anúncios de qualquer objetivo (e que as conversões otimizam). is_default marca o padrão da conta, usado quando a campanha não envia tracking.pixel_id.

```bash
curl -X GET "https://leadgo.com.br/api/v1/ad-account/pixels" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "123456789012345",
            "label": "Pixel da loja · 123456789012345",
            "is_default": true
        }
    ]
}
```

Erros: `ad_account_not_linked`.

#### Formulários de cadastro

`GET /ad-account/lead-forms` · escopo `campaigns:read`

Formulários instantâneos da conta. Use platform_form_id em lead_form_id para uma nova campanha receber os leads no mesmo formulário.

```bash
curl -X GET "https://leadgo.com.br/api/v1/ad-account/lead-forms" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": 7,
            "platform_form_id": "1122334455667788",
            "name": "Tabela de preços",
            "status": "ACTIVE",
            "questions": [
                {
                    "type": "FULL_NAME"
                },
                {
                    "type": "EMAIL"
                },
                {
                    "type": "PHONE"
                }
            ],
            "privacy_policy_url": "https://imobiliaria.exemplo.com.br/privacidade",
            "last_synced_at": "2026-10-28T14:40:00+00:00"
        }
    ]
}
```

Erros: `ad_account_not_linked`.

#### Perfis do Instagram

`GET /ad-account/instagram-accounts` · escopo `campaigns:read`

Perfis do Instagram com que a conta de anúncio pode anunciar. Use o id em instagram_account_id para o anúncio sair com outro perfil que não o ligado à Página. page_backed indica um perfil criado a partir da Página. Cache de 10 minutos.

```bash
curl -X GET "https://leadgo.com.br/api/v1/ad-account/instagram-accounts" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "17841400000000001",
            "username": "lojaexemplo",
            "picture_url": "https://scontent.cdninstagram.com/perfil.jpg",
            "page_backed": false
        }
    ]
}
```

Erros: `ad_account_not_linked`, `platform_quota_exceeded`, `platform_unavailable`.

#### Posts do Instagram

`GET /ad-account/instagram-posts` · escopo `campaigns:read`

Posts do Instagram ligado à conta, dos mais novos para os mais antigos, para escolher o que impulsionar em POST /boosts (use o id em post.id com post.source instagram). Pagine com cursor (meta.next_cursor). Gasta a cota de consultas à plataforma.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| cursor | string | não | meta.next_cursor da página anterior. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/ad-account/instagram-posts" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "17900000000000001",
            "caption": "Chegou a coleção de verão!",
            "media_type": "IMAGE",
            "media_url": "https://scontent.cdninstagram.com/post.jpg",
            "thumbnail_url": "https://scontent.cdninstagram.com/post.jpg",
            "permalink": "https://www.instagram.com/p/AbC123/",
            "published_at": "2026-10-20T14:00:00+0000"
        }
    ],
    "meta": {
        "instagram_user_id": "17841400000000001",
        "username": "lojaexemplo",
        "next_cursor": "QVFIUz..."
    }
}
```

Erros: `ad_account_not_linked`, `platform_quota_exceeded`, `platform_unavailable`.

#### Saúde da conta de anúncio

`GET /ad-account/health` · escopo `campaigns:read`

Confere na Meta o que pode impedir ou atrapalhar os anúncios: situação da conta, forma de pagamento, limite de gasto, Página, Instagram, termos de cadastro, Pixel e WhatsApp. status geral: ok, attention ou blocked; cada checagem: ok, warning, error, missing ou unknown, com o link de correção. Bom para conferir antes de criar campanhas. Cache de 10 minutos.

```bash
curl -X GET "https://leadgo.com.br/api/v1/ad-account/health" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "status": "attention",
        "checked_at": "2026-10-28T15:00:00Z",
        "checks": [
            {
                "id": "payment",
                "label": "Forma de pagamento",
                "status": "ok",
                "status_label": "Tudo certo",
                "title": "Payment method",
                "message": "",
                "fix_label": null,
                "fix_url": null
            },
            {
                "id": "pixel",
                "label": "Pixel",
                "status": "warning",
                "status_label": "Atenção",
                "title": "Pixel",
                "message": "O Pixel não recebe eventos há 7 dias.",
                "fix_label": "Abrir o Gerenciador de Eventos",
                "fix_url": "https://business.facebook.com/events_manager2"
            }
        ]
    }
}
```

Erros: `ad_account_not_linked`, `platform_quota_exceeded`, `platform_unavailable`.

#### Saldo e limite de gasto

`GET /ad-account/finance` · escopo `campaigns:read`

Total gasto, saldo, limite de gasto (null = sem limite) e a forma de pagamento da conta de anúncio, em centavos na moeda dela. Cache de 10 minutos.

```bash
curl -X GET "https://leadgo.com.br/api/v1/ad-account/finance" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "currency": "BRL",
        "balance_cents": 0,
        "amount_spent_cents": 1834590,
        "spend_cap_cents": 5000000,
        "funding_source": "Mastercard *4985"
    }
}
```

Erros: `ad_account_not_linked`, `platform_quota_exceeded`, `platform_unavailable`.

#### Alterar o limite de gasto

`PATCH /ad-account` · escopo `account:manage`

Define o limite de gasto da conta de anúncio: ao chegar nele, a Meta pausa todas as campanhas. null remove o limite. Responde o saldo atualizado.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| spend_cap_cents | integer | sim | Limite em centavos (mínimo 100) ou null para remover. |

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/ad-account" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "spend_cap_cents": 5000000
}'
```

Resposta 200:

```json
{
    "data": {
        "currency": "BRL",
        "balance_cents": 0,
        "amount_spent_cents": 1834590,
        "spend_cap_cents": 5000000,
        "funding_source": "Mastercard *4985"
    }
}
```

Erros: `validation_failed`, `ad_account_not_linked`, `platform_quota_exceeded`, `platform_rejected`.

#### Alertas da conta

`GET /alerts` · escopo `campaigns:read`

O que pede atenção na conta de anúncio, os mais graves primeiro: anúncio reprovado, problema de entrega, pagamento, verba no limite, gasto sem resultado e conta restrita. Atualizados de hora em hora e pelos avisos da Meta; cada alerta novo também chega pelo webhook alert.created.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| status | string | não | open (padrão) ou all (inclui resolvidos e dispensados, os 100 mais recentes). |

```bash
curl -X GET "https://leadgo.com.br/api/v1/alerts" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": 31,
            "kind": "spend_no_results",
            "kind_label": "Gasto sem resultado",
            "severity": "warning",
            "title": "Gastando sem resultado",
            "message": "A campanha gastou em 3 dos últimos 7 dias sem nenhuma conversão.",
            "entity": {
                "level": "campaign",
                "platform_id": "120212345678900001",
                "name": "Promoção de outubro"
            },
            "fix": {
                "label": "Abrir no Gerenciador",
                "url": "https://adsmanager.facebook.com/adsmanager/manage/campaigns"
            },
            "status": "open",
            "detected_at": "2026-10-28T09:00:00+00:00",
            "dismissed_at": null,
            "resolved_at": null
        }
    ]
}
```

Erros: `validation_failed`, `ad_account_not_linked`.

#### Dispensar alerta

`POST /alerts/{id}/dismiss` · escopo `account:manage`

Esconde o alerta (status dismissed). Se o problema continuar, ele volta depois de 30 dias.

```bash
curl -X POST "https://leadgo.com.br/api/v1/alerts/{id}/dismiss" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": 31,
        "kind": "spend_no_results",
        "kind_label": "Gasto sem resultado",
        "severity": "warning",
        "title": "Gastando sem resultado",
        "message": "A campanha gastou em 3 dos últimos 7 dias sem nenhuma conversão.",
        "entity": {
            "level": "campaign",
            "platform_id": "120212345678900001",
            "name": "Promoção de outubro"
        },
        "fix": {
            "label": "Abrir no Gerenciador",
            "url": "https://adsmanager.facebook.com/adsmanager/manage/campaigns"
        },
        "status": "dismissed",
        "detected_at": "2026-10-28T09:00:00+00:00",
        "dismissed_at": "2026-10-28T16:00:00+00:00",
        "resolved_at": null
    }
}
```

Erros: `not_found`, `platform_unavailable`.

#### Sincronizar com a Meta

`POST /ad-account/sync` · escopo `campaigns:read`

Pede uma sincronização das campanhas (status, conjuntos, anúncios). As rotinas já sincronizam de hora em hora; use depois de mudar algo direto no Gerenciador de Anúncios. No máximo uma a cada 5 minutos.

```bash
curl -X POST "https://leadgo.com.br/api/v1/ad-account/sync" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 202:

```json
{
    "data": {
        "queued": true
    }
}
```

Erros: `ad_account_not_linked`, `sync_too_soon`.

### Listas e segmentação

Valores aceitos na criação, busca de cidades e interesses e estimativa de alcance.

#### Valores aceitos

`GET /reference` · escopo `campaigns:read`

Objetivos (e o que cada um exige: link, Pixel, vídeo, WhatsApp), botões, países, eventos de conversão, categorias especiais, campos de formulário, tipos de mídia, limites, escopos e eventos de webhook. Os mesmos do painel. Em limits, max_url_tags conta os parâmetros de URL extras, na campanha e no anúncio: os 4 UTMs padrão (utm_source, utm_medium, utm_campaign e utm_content) ficam fora da conta.

```bash
curl -X GET "https://leadgo.com.br/api/v1/reference" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "goals": [
            {
                "value": "traffic",
                "label": "Tráfego",
                "description": "Leva pessoas para o seu site, loja ou WhatsApp.",
                "needs_link": true,
                "requires_pixel": false,
                "requires_video": false,
                "uses_messaging": false
            }
        ],
        "call_to_actions": [
            {
                "value": "LEARN_MORE",
                "label": "Saiba mais"
            }
        ],
        "countries": [
            {
                "value": "BR",
                "label": "Brasil"
            }
        ],
        "conversion_events": [
            {
                "value": "PURCHASE",
                "label": "Compra"
            }
        ],
        "limits": {
            "max_creatives": 10,
            "max_cities": 25,
            "max_interests": 25,
            "max_interest_groups": 3,
            "max_url_tags": 10
        }
    }
}
```

#### Buscar cidades

`GET /targeting/cities` · escopo `campaigns:read`

Cidades da segmentação da Meta. Use key e name em targeting.cities. Resultados ficam em cache por um dia; buscas novas gastam a cota de consultas à plataforma.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| q | string | sim | Nome da cidade (mínimo 2 letras). |
| country | string | não | Restringe a um país (BR, AR...). |

```bash
curl -X GET "https://leadgo.com.br/api/v1/targeting/cities?q=Campinas&country=BR" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "key": "2490299",
            "name": "Campinas, São Paulo",
            "type": "Cidade",
            "detail": "São Paulo, Brasil",
            "country_code": "BR"
        }
    ]
}
```

Erros: `validation_failed`, `platform_quota_exceeded`, `ad_account_not_linked`.

#### Buscar interesses

`GET /targeting/interests` · escopo `campaigns:read`

Interesses da segmentação da Meta. Use id e name em targeting.interests (ou num grupo de targeting.narrow_interests).

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| q | string | sim | Termo (mínimo 2 letras). |

```bash
curl -X GET "https://leadgo.com.br/api/v1/targeting/interests?q=corrida" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "6003397425735",
            "name": "Corrida",
            "audience_size": 48000000
        }
    ]
}
```

Erros: `validation_failed`, `platform_quota_exceeded`, `ad_account_not_linked`.

#### Estimar alcance

`POST /targeting/reach-estimate` · escopo `campaigns:read`

Quantas pessoas cabem num público, segundo a Meta, antes de criar a campanha. Recebe o mesmo persona_id e o mesmo objeto targeting de POST /campaigns (com exclusões, idiomas e grupos de interesses). Quando a Meta ainda está calculando, ready vem false: tente de novo em instantes. Com targeting.narrow_interests, cada grupo é estimado à parte (uma consulta à plataforma por grupo): narrowed vem true, upper é o teto do menor grupo e lower vem null (o cruzamento a Meta só calcula depois de publicar). warning traz um aviso quando o público pode ser pequeno demais para a Meta entregar (senão, null). Cache de 1 hora para a mesma segmentação.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| persona_id | integer | não | Persona da conta como base do público. |
| targeting | object | não | O mesmo objeto targeting de POST /campaigns. |

```bash
curl -X POST "https://leadgo.com.br/api/v1/targeting/reach-estimate" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "targeting": {
        "cities": [
            {
                "key": "269969",
                "name": "São Paulo, São Paulo",
                "country_code": "BR",
                "radius_km": 25
            }
        ],
        "age_min": 25,
        "age_max": 45,
        "gender": "female",
        "interests": [
            {
                "id": "6003107902433",
                "name": "Compras online"
            }
        ]
    }
}'
```

Resposta 200:

```json
{
    "data": {
        "available": true,
        "lower": 1200000,
        "upper": 1450000,
        "ready": true,
        "narrowed": false,
        "warning": null
    }
}
```

Erros: `validation_failed`, `ad_account_not_linked`, `platform_quota_exceeded`, `platform_unavailable`.

### Mídias

Imagens e vídeos dos anúncios. Envie uma vez e use o id em quantas campanhas quiser.

#### Enviar mídia

`POST /media` · escopo `media:write`

Envie o arquivo (multipart, campo file) e ele fica pronto na hora (201); ou informe a URL (JSON, campo url) e o download segue em segundo plano (202, status pending → ready ou failed, com o aviso media.ready). Imagens JPG/PNG até 30 MB; vídeos MP4/MOV até 200 MB; o tipo é conferido pelo conteúdo. A URL precisa ser https, pública e direta (links de compartilhamento do Drive e do Dropbox redirecionam e falham). Mídias sem uso somem depois de 7 dias; usadas, 30 dias depois do último uso.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| file | file | não | O arquivo (multipart/form-data). Obrigatório sem url. |
| url | string | não | URL https direta do arquivo (application/json). Obrigatório sem file. |
| filename | string | não | Nome de exibição. |

```bash
curl -X POST "https://leadgo.com.br/api/v1/media" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -F "file=@outubro.jpg"

# Ou por URL (o download segue em segundo plano):
curl -X POST "https://leadgo.com.br/api/v1/media" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://cdn.loja.exemplo.com.br/banners/outubro.jpg"
}'
```

Resposta 201:

```json
{
    "data": {
        "id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
        "status": "ready",
        "kind": "image",
        "filename": "outubro.jpg",
        "mime_type": "image/jpeg",
        "size": 482113,
        "source_url": null,
        "error": null,
        "ready_at": "2026-10-28T15:00:02+00:00",
        "last_used_at": null,
        "expires_at": "2026-11-04T15:00:02+00:00",
        "created_at": "2026-10-28T15:00:02+00:00"
    }
}
```

Erros: `validation_failed`, `media_rejected`, `too_many_imports`.

#### Listar mídias

`GET /media` · escopo `media:write`

Mídias da conta, das mais novas para as mais antigas (paginado por cursor, 50 por página).

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| cursor | string | não | Valor de meta.next_cursor da página anterior. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/media" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "status": "ready",
            "kind": "image",
            "filename": "outubro.jpg",
            "mime_type": "image/jpeg",
            "size": 482113,
            "source_url": null,
            "error": null,
            "ready_at": "2026-10-28T15:00:02+00:00",
            "last_used_at": null,
            "expires_at": "2026-11-04T15:00:02+00:00",
            "created_at": "2026-10-28T15:00:02+00:00"
        }
    ],
    "links": {
        "first": null,
        "last": null,
        "prev": null,
        "next": null
    },
    "meta": {
        "path": "/api/v1/media",
        "per_page": 50,
        "next_cursor": null,
        "prev_cursor": null
    }
}
```

#### Consultar mídia

`GET /media/{id}` · escopo `media:write`

Situação de uma mídia: use para acompanhar o download por URL (status pending → ready ou failed, com o motivo em error).

```bash
curl -X GET "https://leadgo.com.br/api/v1/media/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
        "status": "ready",
        "kind": "image",
        "filename": "outubro.jpg",
        "mime_type": "image/jpeg",
        "size": 482113,
        "source_url": null,
        "error": null,
        "ready_at": "2026-10-28T15:00:02+00:00",
        "last_used_at": null,
        "expires_at": "2026-11-04T15:00:02+00:00",
        "created_at": "2026-10-28T15:00:02+00:00"
    }
}
```

Erros: `not_found`.

#### Excluir mídia

`DELETE /media/{id}` · escopo `media:write`

Apaga a mídia da biblioteca. Campanhas já criadas com ela não são afetadas (cada uma tem a sua cópia).

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/media/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`.

### Campanhas

Criar, consultar, alterar, duplicar e excluir campanhas, pelas mesmas rotinas do painel.

#### Listar campanhas

`GET /campaigns` · escopo `campaigns:read`

Campanhas da conta (criadas pela API, pelo painel e as importadas da Meta), das mais novas para as mais antigas, paginadas por cursor.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| status | string | não | creating, failed, pending_review, active, paused, rejected, error, completed ou cancelled. |
| goal | string | não | Objetivo (veja GET /reference). |
| search | string | não | Parte do nome da campanha. |
| limit | integer | não | Itens por página, de 1 a 100 (padrão 25). |
| cursor | string | não | Valor de meta.next_cursor (ou siga links.next). |

```bash
curl -X GET "https://leadgo.com.br/api/v1/campaigns?status=active&limit=25" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Promoção de outubro",
            "goal": "traffic",
            "goal_label": "Tráfego",
            "status": "active",
            "status_label": "Ativa",
            "created_via": "api",
            "budget": {
                "level": "campaign",
                "type": "daily",
                "amount_cents": 5000,
                "currency": "BRL"
            },
            "schedule": {
                "starts_at": "2026-10-28T03:00:00+00:00",
                "ends_at": null
            },
            "error": null,
            "platform": "meta",
            "platform_campaign_id": "120212345678900001",
            "published_at": "2026-10-28T15:02:11+00:00",
            "synced_at": "2026-10-28T15:40:00+00:00",
            "created_at": "2026-10-28T15:00:00+00:00",
            "updated_at": "2026-10-28T15:02:11+00:00",
            "links": {
                "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
            }
        }
    ],
    "links": {
        "first": null,
        "last": null,
        "prev": null,
        "next": "https://app.leadgo.com.br/api/v1/campaigns?cursor=eyJjcmVhdGVkX2F0Ijo..."
    },
    "meta": {
        "path": "https://app.leadgo.com.br/api/v1/campaigns",
        "per_page": 25,
        "next_cursor": "eyJjcmVhdGVkX2F0Ijo...",
        "prev_cursor": null
    }
}
```

Erros: `validation_failed`.

#### Criar campanha

`POST /campaigns` · escopo `campaigns:write` · aceita `Idempotency-Key`

Cria a campanha pelas mesmas rotinas do painel: ela é gravada com status creating e a publicação na Meta segue pela fila (a resposta é 202, com Location). O fim chega pelo webhook campaign.published ou campaign.failed, ou consultando GET /campaigns/{id}. Com ?validate_only=true nada é criado: a resposta traz o rascunho como seria publicado, com os padrões aplicados (Pixel, datas, público). Os campos que faltarem seguem os padrões do assistente do painel. Cada item de creatives é um anúncio do mesmo conjunto: arte ou vídeo (media_id) ou carrossel (cards, um por campanha), até 10; do segundo item em diante, o título, o texto, a descrição e o botão que faltarem vêm do primeiro. Na resposta, um carrossel sozinho volta no formato antigo (format carousel, carousel.cards); junto de arte ou vídeo, volta no próprio item (media_type carousel, cards). Todo anúncio novo, de qualquer objetivo, sai com "Eventos do site" marcado (o Pixel é obrigatório: veja tracking.pixel_id; sem Pixel para escolher, 422 nesse campo) e sempre com os 4 UTMs padrão; tracking.url_tags acrescenta extras ou troca o valor de um dos 4.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| validate_only | boolean | não | Só confere o corpo (200, nada é criado). |

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | sim | Nome da campanha (até 120 caracteres). |
| goal | string | sim | traffic, awareness, engagement, video_views, conversions, lead_generation ou whatsapp_messages. |
| status | string | não | paused (padrão) ou active: como a campanha nasce depois de aprovada. |
| budget.type | string | não | daily (padrão) ou lifetime (total, exige schedule.end_date). |
| budget.amount_cents | integer | sim | Valor em centavos (5000 = R$ 50,00). A diária respeita a mínima da conta. |
| schedule.start_date | string | não | AAAA-MM-DD no fuso da conta (padrão: hoje). |
| schedule.end_date | string | não | AAAA-MM-DD, depois do início. Obrigatória no orçamento total. |
| tracking.pixel_id | string | não | Pixel que marca "Eventos do site" nos anúncios, em qualquer objetivo (nas conversões, é também o Pixel que elas otimizam). Obrigatório: todo anúncio novo sai com ele. Sem pixel_id, vale o padrão da conta (default_pixel_id em GET /ad-account) e, sem padrão, o único que ela enxerga (GET /ad-account/pixels), que passa a ser o padrão. Com vários Pixels e nenhum padrão, sem nenhum Pixel ou com a consulta fora do ar, a campanha é recusada com 422 neste campo. Nas respostas, mostra o Pixel escolhido. |
| tracking.conversion_event | string | não | PURCHASE (padrão), LEAD, COMPLETE_REGISTRATION, ADD_TO_CART ou INITIATED_CHECKOUT. |
| special_ad_category | string | não | HOUSING, EMPLOYMENT, CREDIT, FINANCIAL_PRODUCTS_SERVICES ou ISSUES_ELECTIONS_POLITICS (anúncios desses temas têm segmentação restrita pela Meta). |
| persona_id | integer | não | Persona da conta: a base do público (targeting sobrescreve campo a campo). |
| targeting.countries | string[] | não | Países (padrão BR). Com cidades, o país sai delas. |
| targeting.cities | object[] | não | Cidades {key, name, country_code} de GET /targeting/cities (até 25). |
| targeting.age_min | integer | não | 13 a 65 (padrão 18). |
| targeting.age_max | integer | não | 13 a 65, maior ou igual à mínima (padrão 65). |
| targeting.gender | string | não | all (padrão), male ou female. |
| targeting.interests | object[] | não | Interesses {id, name} de GET /targeting/interests (até 25): basta ter um deles (OU). |
| targeting.narrow_interests | object[][] | não | Grupos que o público também precisa ter: um interesse de cada grupo (E), além de um da lista principal (OU). Até 3 grupos de até 25 interesses {id, name}. Não combina com Advantage+ público. Com persona, [] tira os grupos dela. |
| targeting.cities[].radius_km | integer | não | Raio em volta da cidade: 17, 25, 40, 50, 80 km (sem raio, só a cidade). |
| targeting.audience_id | string | não | Público da conta a incluir (platform_audience_id de GET /audiences). |
| targeting.excluded_audience_ids | string[] | não | Públicos a excluir (até 10), como quem já comprou. |
| targeting.excluded_cities | object[] | não | Cidades a excluir {key, name} de GET /targeting/cities (até 25). |
| targeting.languages | string[] | não | pt, es, en (sem idioma, todos). |
| targeting.advantage_audience | boolean | não | Deixa a Meta ampliar o público além do definido (Advantage+ público). Padrão false. Não vale com targeting.narrow_interests. |
| placements | string[] | não | Posicionamentos manuais (veja GET /reference). Vazio ou ausente: automáticos, o recomendado. |
| tracking.url_tags | object[] | não | UTMs extras {key, value}, somadas ao link de todos os anúncios. Os 4 padrão vão sempre (utm_source=facebook, utm_medium=paid, utm_campaign={{campaign.name}}, utm_content={{ad.name}}): ausente ou [], a campanha sai só com eles. Um extra com a chave de um dos 4 troca o valor dele (ex.: utm_campaign=blackfriday); os demais vêm depois, na ordem enviada. Até 10 extras: as chaves dos 4 não contam, então reenviar a lista de uma resposta passa. Aceita as macros da Meta, como {{campaign.name}} e {{ad.name}}. Vale em todo objetivo, inclusive cadastro e WhatsApp. Nas respostas, mostra a lista efetiva: os 4 (com o valor trocado, quando houver) e os extras. |
| instagram_account_id | string | não | Perfil do Instagram do anúncio (GET /ad-account/instagram-accounts). Sem ele, o ligado à Página. Não vale no WhatsApp. |
| bidding.strategy | string | não | COST_CAP (custo por resultado), LOWEST_COST_WITH_BID_CAP (lance máximo) ou LOWEST_COST_WITH_MIN_ROAS (ROAS mínimo, só vendas com Compra). Sem ela, lance automático. |
| bidding.amount_cents | integer | não | Valor em centavos, obrigatório em COST_CAP e LOWEST_COST_WITH_BID_CAP. |
| bidding.roas_floor | number | não | Retorno mínimo (2.5 = 2,5x), obrigatório em LOWEST_COST_WITH_MIN_ROAS. |
| format | string | não | single (padrão) ou carousel, o formato antigo do carrossel sozinho (segue aceito): um criativo só (texto principal, título e botão) e as imagens em carousel.cards. Para juntar o carrossel com arte ou vídeo, use creatives[].cards. |
| carousel.cards[].media_id | string | não | Formato antigo: imagem pronta da biblioteca de cada cartão (2 a 10 cartões). |
| carousel.cards[].headline | string | não | Título do cartão (obrigatório em cada cartão). |
| carousel.cards[].description | string | não | Descrição do cartão. |
| carousel.cards[].link_url | string | não | Link do cartão (padrão: link_url). |
| carousel.cards[].call_to_action | string | não | Botão do cartão (padrão: o do criativo). |
| carousel.optimize_order | boolean | não | true (padrão): a Meta reordena os cartões pelo desempenho. false: ordem fixa, até 5 cartões. |
| text_variations.headlines | string[] | não | Até 4 títulos alternativos que a Meta alterna (só num anúncio simples: um criativo, sem carrossel, objetivos com link). |
| text_variations.bodies | string[] | não | Até 4 textos principais alternativos. |
| link_url | string | não | URL https de destino. Obrigatória nos objetivos com link (veja needs_link em GET /reference). |
| creatives[].media_id | string | não | Arte ou vídeo pronto da biblioteca (POST /media): o tipo do anúncio sai da mídia. Cada item de creatives é um anúncio do mesmo conjunto, até 10. Cada item leva media_id ou cards, nunca os dois. |
| creatives[].headline | string | sim | Título (até 255). Obrigatório no primeiro item; nos demais, sem ele, vale o do primeiro. |
| creatives[].body | string | sim | Texto principal (até 2000). Obrigatório no primeiro item; nos demais, sem ele, vale o do primeiro. |
| creatives[].description | string | não | Descrição (até 255). Do segundo item em diante, sem ela, vale a do primeiro; envie null para o anúncio sair sem descrição. |
| creatives[].call_to_action | string | não | Botão (padrão LEARN_MORE; veja GET /reference). Do segundo item em diante, sem ele, vale o do primeiro; null volta ao LEARN_MORE. No WhatsApp, ignorado. |
| creatives[].cards | object[] | não | Carrossel: de 2 a 10 cartões, só imagens. Um carrossel por campanha, ao lado de artes e vídeos. Vale em todos os objetivos, menos video_views. |
| creatives[].cards[].media_id | string | não | Imagem (JPG ou PNG) da biblioteca para o cartão (obrigatória em cada cartão). |
| creatives[].cards[].headline | string | não | Título do cartão (obrigatório em cada cartão). |
| creatives[].cards[].description | string | não | Descrição do cartão. |
| creatives[].cards[].link_url | string | não | Link do cartão (padrão: link_url). No WhatsApp, não envie: o toque no cartão abre a conversa. |
| creatives[].cards[].call_to_action | string | não | Botão do cartão (padrão: o do anúncio). No WhatsApp, não envie: o botão é sempre o de conversa. |
| creatives[].optimize_order | boolean | não | No item com cards: true (padrão), a Meta reordena os cartões pelo desempenho; false, ordem fixa, até 5 cartões. |
| whatsapp.greeting | string | não | Boas-vindas da empresa na conversa (objetivo whatsapp_messages). |
| whatsapp.prefill | string | não | Mensagem que já vem digitada para o cliente enviar. |
| whatsapp.phone_number | string | não | Número em E.164 (+5511999998888); sem ele, o da Página. |
| lead_form_id | string | não | Formulário existente (platform_form_id de GET /ad-account/lead-forms), no objetivo lead_generation. |
| lead_form.name | string | não | Nome do formulário novo (padrão: o da campanha). |
| lead_form.fields | string[] | não | Campos padrão: FULL_NAME, EMAIL, PHONE, CITY, STATE, ZIP, COMPANY_NAME, JOB_TITLE (nome e telefone sempre entram). |
| lead_form.questions | object[] | não | Até 3 perguntas {question, options[]} (sem options, resposta livre). |
| lead_form.privacy_policy_url | string | não | Política de privacidade (padrão: a de Leads › Configurações; a Meta exige). |
| lead_form.thank_you.title | string | não | Título da tela final (obrigatório com lead_form). |
| lead_form.thank_you.body | string | não | Texto da tela final (obrigatório com lead_form). |

```bash
curl -X POST "https://leadgo.com.br/api/v1/campaigns" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Promoção de outubro",
    "goal": "traffic",
    "status": "paused",
    "budget": {
        "type": "daily",
        "amount_cents": 5000
    },
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 25,
        "age_max": 55,
        "gender": "all"
    },
    "link_url": "https://loja.exemplo.com.br/outubro",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Outono com 30% off",
            "body": "Peças selecionadas com frete grátis até domingo.",
            "call_to_action": "SHOP_NOW"
        }
    ]
}'
```

Resposta 202:

```json
{
    "data": {
        "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "name": "Promoção de outubro",
        "goal": "traffic",
        "goal_label": "Tráfego",
        "status": "creating",
        "status_label": "Publicando",
        "created_via": "api",
        "budget": {
            "level": "campaign",
            "type": "daily",
            "amount_cents": 5000,
            "currency": "BRL"
        },
        "schedule": {
            "starts_at": "2026-10-28T03:00:00+00:00",
            "ends_at": null
        },
        "error": null,
        "platform": "meta",
        "platform_campaign_id": null,
        "published_at": null,
        "synced_at": null,
        "created_at": "2026-10-28T15:00:00+00:00",
        "updated_at": "2026-10-28T15:02:11+00:00",
        "draft": {
            "name": "Promoção de outubro",
            "goal": "traffic",
            "status": "paused",
            "budget": {
                "type": "daily",
                "amount_cents": 5000
            },
            "schedule": {
                "start_date": "2026-10-28",
                "end_date": null
            },
            "tracking": {
                "pixel_id": "123456789012345",
                "conversion_event": null,
                "url_tags": [
                    {
                        "key": "utm_source",
                        "value": "facebook"
                    },
                    {
                        "key": "utm_medium",
                        "value": "paid"
                    },
                    {
                        "key": "utm_campaign",
                        "value": "{{campaign.name}}"
                    },
                    {
                        "key": "utm_content",
                        "value": "{{ad.name}}"
                    }
                ]
            },
            "special_ad_category": null,
            "targeting": {
                "countries": [
                    "BR"
                ],
                "cities": [],
                "age_min": 25,
                "age_max": 55,
                "gender": "all",
                "interests": [],
                "narrow_interests": [],
                "audience_id": null
            },
            "link_url": "https://loja.exemplo.com.br/outubro",
            "creatives": [
                {
                    "media_type": "image",
                    "headline": "Outono com 30% off",
                    "body": "Peças selecionadas com frete grátis até domingo.",
                    "call_to_action": "SHOP_NOW"
                }
            ],
            "whatsapp": null,
            "lead_form": null
        },
        "ad_sets": [],
        "brain": {
            "mode": null,
            "effective_mode": "manual",
            "target_cpa_cents": null,
            "target_roas": null,
            "effective_target_cpa_cents": 3000,
            "effective_target_roas": null,
            "locked_until": null,
            "last_touched_at": null,
            "last_touched_by": null
        },
        "links": {
            "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
        }
    }
}
```

Erros: `validation_failed`, `ad_account_not_linked`, `too_many_publishing`, `campaign_creation_limit`, `idempotency_key_reused`, `idempotency_in_progress`.

#### Prévia antes de criar

`POST /campaigns/preview` · escopo `campaigns:write`

Recebe o mesmo corpo de POST /campaigns e devolve a prévia da Meta de cada posicionamento, sem criar nada. Com vários anúncios, escolha qual em ?creative= (o primeiro é 0): o carrossel é o anúncio da posição dele em creatives e aparece com todos os cartões; no carrossel sozinho, é sempre 0. url é o endereço da prévia para um <iframe> (expira em minutos). Vale para arte e carrossel em anúncios com link; vídeo e WhatsApp respondem preview_unavailable.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| creative | integer | não | Posição do anúncio em creatives (padrão 0). |
| formats | string | não | Formatos separados por vírgula (veja GET /ads/{id}/preview). |

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | sim | Nome da campanha (até 120 caracteres). |
| goal | string | sim | traffic, awareness, engagement, video_views, conversions, lead_generation ou whatsapp_messages. |
| status | string | não | paused (padrão) ou active: como a campanha nasce depois de aprovada. |
| budget.type | string | não | daily (padrão) ou lifetime (total, exige schedule.end_date). |
| budget.amount_cents | integer | sim | Valor em centavos (5000 = R$ 50,00). A diária respeita a mínima da conta. |
| schedule.start_date | string | não | AAAA-MM-DD no fuso da conta (padrão: hoje). |
| schedule.end_date | string | não | AAAA-MM-DD, depois do início. Obrigatória no orçamento total. |
| tracking.pixel_id | string | não | Pixel que marca "Eventos do site" nos anúncios, em qualquer objetivo (nas conversões, é também o Pixel que elas otimizam). Obrigatório: todo anúncio novo sai com ele. Sem pixel_id, vale o padrão da conta (default_pixel_id em GET /ad-account) e, sem padrão, o único que ela enxerga (GET /ad-account/pixels), que passa a ser o padrão. Com vários Pixels e nenhum padrão, sem nenhum Pixel ou com a consulta fora do ar, a campanha é recusada com 422 neste campo. Nas respostas, mostra o Pixel escolhido. |
| tracking.conversion_event | string | não | PURCHASE (padrão), LEAD, COMPLETE_REGISTRATION, ADD_TO_CART ou INITIATED_CHECKOUT. |
| special_ad_category | string | não | HOUSING, EMPLOYMENT, CREDIT, FINANCIAL_PRODUCTS_SERVICES ou ISSUES_ELECTIONS_POLITICS (anúncios desses temas têm segmentação restrita pela Meta). |
| persona_id | integer | não | Persona da conta: a base do público (targeting sobrescreve campo a campo). |
| targeting.countries | string[] | não | Países (padrão BR). Com cidades, o país sai delas. |
| targeting.cities | object[] | não | Cidades {key, name, country_code} de GET /targeting/cities (até 25). |
| targeting.age_min | integer | não | 13 a 65 (padrão 18). |
| targeting.age_max | integer | não | 13 a 65, maior ou igual à mínima (padrão 65). |
| targeting.gender | string | não | all (padrão), male ou female. |
| targeting.interests | object[] | não | Interesses {id, name} de GET /targeting/interests (até 25): basta ter um deles (OU). |
| targeting.narrow_interests | object[][] | não | Grupos que o público também precisa ter: um interesse de cada grupo (E), além de um da lista principal (OU). Até 3 grupos de até 25 interesses {id, name}. Não combina com Advantage+ público. Com persona, [] tira os grupos dela. |
| targeting.cities[].radius_km | integer | não | Raio em volta da cidade: 17, 25, 40, 50, 80 km (sem raio, só a cidade). |
| targeting.audience_id | string | não | Público da conta a incluir (platform_audience_id de GET /audiences). |
| targeting.excluded_audience_ids | string[] | não | Públicos a excluir (até 10), como quem já comprou. |
| targeting.excluded_cities | object[] | não | Cidades a excluir {key, name} de GET /targeting/cities (até 25). |
| targeting.languages | string[] | não | pt, es, en (sem idioma, todos). |
| targeting.advantage_audience | boolean | não | Deixa a Meta ampliar o público além do definido (Advantage+ público). Padrão false. Não vale com targeting.narrow_interests. |
| placements | string[] | não | Posicionamentos manuais (veja GET /reference). Vazio ou ausente: automáticos, o recomendado. |
| tracking.url_tags | object[] | não | UTMs extras {key, value}, somadas ao link de todos os anúncios. Os 4 padrão vão sempre (utm_source=facebook, utm_medium=paid, utm_campaign={{campaign.name}}, utm_content={{ad.name}}): ausente ou [], a campanha sai só com eles. Um extra com a chave de um dos 4 troca o valor dele (ex.: utm_campaign=blackfriday); os demais vêm depois, na ordem enviada. Até 10 extras: as chaves dos 4 não contam, então reenviar a lista de uma resposta passa. Aceita as macros da Meta, como {{campaign.name}} e {{ad.name}}. Vale em todo objetivo, inclusive cadastro e WhatsApp. Nas respostas, mostra a lista efetiva: os 4 (com o valor trocado, quando houver) e os extras. |
| instagram_account_id | string | não | Perfil do Instagram do anúncio (GET /ad-account/instagram-accounts). Sem ele, o ligado à Página. Não vale no WhatsApp. |
| bidding.strategy | string | não | COST_CAP (custo por resultado), LOWEST_COST_WITH_BID_CAP (lance máximo) ou LOWEST_COST_WITH_MIN_ROAS (ROAS mínimo, só vendas com Compra). Sem ela, lance automático. |
| bidding.amount_cents | integer | não | Valor em centavos, obrigatório em COST_CAP e LOWEST_COST_WITH_BID_CAP. |
| bidding.roas_floor | number | não | Retorno mínimo (2.5 = 2,5x), obrigatório em LOWEST_COST_WITH_MIN_ROAS. |
| format | string | não | single (padrão) ou carousel, o formato antigo do carrossel sozinho (segue aceito): um criativo só (texto principal, título e botão) e as imagens em carousel.cards. Para juntar o carrossel com arte ou vídeo, use creatives[].cards. |
| carousel.cards[].media_id | string | não | Formato antigo: imagem pronta da biblioteca de cada cartão (2 a 10 cartões). |
| carousel.cards[].headline | string | não | Título do cartão (obrigatório em cada cartão). |
| carousel.cards[].description | string | não | Descrição do cartão. |
| carousel.cards[].link_url | string | não | Link do cartão (padrão: link_url). |
| carousel.cards[].call_to_action | string | não | Botão do cartão (padrão: o do criativo). |
| carousel.optimize_order | boolean | não | true (padrão): a Meta reordena os cartões pelo desempenho. false: ordem fixa, até 5 cartões. |
| text_variations.headlines | string[] | não | Até 4 títulos alternativos que a Meta alterna (só num anúncio simples: um criativo, sem carrossel, objetivos com link). |
| text_variations.bodies | string[] | não | Até 4 textos principais alternativos. |
| link_url | string | não | URL https de destino. Obrigatória nos objetivos com link (veja needs_link em GET /reference). |
| creatives[].media_id | string | não | Arte ou vídeo pronto da biblioteca (POST /media): o tipo do anúncio sai da mídia. Cada item de creatives é um anúncio do mesmo conjunto, até 10. Cada item leva media_id ou cards, nunca os dois. |
| creatives[].headline | string | sim | Título (até 255). Obrigatório no primeiro item; nos demais, sem ele, vale o do primeiro. |
| creatives[].body | string | sim | Texto principal (até 2000). Obrigatório no primeiro item; nos demais, sem ele, vale o do primeiro. |
| creatives[].description | string | não | Descrição (até 255). Do segundo item em diante, sem ela, vale a do primeiro; envie null para o anúncio sair sem descrição. |
| creatives[].call_to_action | string | não | Botão (padrão LEARN_MORE; veja GET /reference). Do segundo item em diante, sem ele, vale o do primeiro; null volta ao LEARN_MORE. No WhatsApp, ignorado. |
| creatives[].cards | object[] | não | Carrossel: de 2 a 10 cartões, só imagens. Um carrossel por campanha, ao lado de artes e vídeos. Vale em todos os objetivos, menos video_views. |
| creatives[].cards[].media_id | string | não | Imagem (JPG ou PNG) da biblioteca para o cartão (obrigatória em cada cartão). |
| creatives[].cards[].headline | string | não | Título do cartão (obrigatório em cada cartão). |
| creatives[].cards[].description | string | não | Descrição do cartão. |
| creatives[].cards[].link_url | string | não | Link do cartão (padrão: link_url). No WhatsApp, não envie: o toque no cartão abre a conversa. |
| creatives[].cards[].call_to_action | string | não | Botão do cartão (padrão: o do anúncio). No WhatsApp, não envie: o botão é sempre o de conversa. |
| creatives[].optimize_order | boolean | não | No item com cards: true (padrão), a Meta reordena os cartões pelo desempenho; false, ordem fixa, até 5 cartões. |
| whatsapp.greeting | string | não | Boas-vindas da empresa na conversa (objetivo whatsapp_messages). |
| whatsapp.prefill | string | não | Mensagem que já vem digitada para o cliente enviar. |
| whatsapp.phone_number | string | não | Número em E.164 (+5511999998888); sem ele, o da Página. |
| lead_form_id | string | não | Formulário existente (platform_form_id de GET /ad-account/lead-forms), no objetivo lead_generation. |
| lead_form.name | string | não | Nome do formulário novo (padrão: o da campanha). |
| lead_form.fields | string[] | não | Campos padrão: FULL_NAME, EMAIL, PHONE, CITY, STATE, ZIP, COMPANY_NAME, JOB_TITLE (nome e telefone sempre entram). |
| lead_form.questions | object[] | não | Até 3 perguntas {question, options[]} (sem options, resposta livre). |
| lead_form.privacy_policy_url | string | não | Política de privacidade (padrão: a de Leads › Configurações; a Meta exige). |
| lead_form.thank_you.title | string | não | Título da tela final (obrigatório com lead_form). |
| lead_form.thank_you.body | string | não | Texto da tela final (obrigatório com lead_form). |

```bash
curl -X POST "https://leadgo.com.br/api/v1/campaigns/preview?formats=INSTAGRAM_STORY%2CMOBILE_FEED_STANDARD" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Promoção de outubro",
    "goal": "traffic",
    "status": "paused",
    "budget": {
        "type": "daily",
        "amount_cents": 5000
    },
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 25,
        "age_max": 55,
        "gender": "all"
    },
    "link_url": "https://loja.exemplo.com.br/outubro",
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Outono com 30% off",
            "body": "Peças selecionadas com frete grátis até domingo.",
            "call_to_action": "SHOP_NOW"
        }
    ]
}'
```

Resposta 200:

```json
{
    "data": {
        "creative": 0,
        "previews": [
            {
                "format": "INSTAGRAM_STORY",
                "label": "Stories do Instagram",
                "url": "https://www.facebook.com/ads/api/preview_iframe.php?d=AQ...&t=AQ...",
                "html": "<iframe src=\"https://www.facebook.com/ads/api/preview_iframe.php?d=AQ...&t=AQ...\" width=\"274\" height=\"213\" scrolling=\"yes\" style=\"border: none;\"></iframe>"
            }
        ]
    }
}
```

Erros: `validation_failed`, `preview_unavailable`, `ad_account_not_linked`, `platform_quota_exceeded`, `platform_rejected`.

#### Impulsionar post

`POST /boosts` · escopo `campaigns:write` · aceita `Idempotency-Key`

Transforma um post que já está no Facebook ou no Instagram em anúncio, mantendo as curtidas e os comentários. Cria campanha, conjunto e anúncio pela mesma fila da criação (202, status creating; o fim chega por campaign.published ou campaign.failed). Posts do Instagram vêm de GET /ad-account/instagram-posts; no Facebook, use {idDaPágina}_{idDoPost}. Objetivos: engagement, traffic, awareness, video_views (tráfego leva link_url e call_to_action). O criativo de um post impulsionado não muda depois (boost_creative_locked). O anúncio sai com "Eventos do site" marcado, com o Pixel escolhido como em POST /campaigns (tracking.pixel_id, o padrão da conta ou o único que ela enxerga; sem Pixel para escolher, 422 em tracking.pixel_id, também no validate_only), e com os 4 UTMs padrão, em todo objetivo (o impulsionamento não aceita extras). Com ?validate_only=true só confere o corpo.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| validate_only | boolean | não | Só confere (200, nada é criado). |

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | sim | Nome da campanha. |
| goal | string | sim | engagement, traffic, awareness, video_views. |
| post.source | string | sim | instagram ou facebook. |
| post.id | string | sim | Id da mídia do Instagram ou do post do Facebook ({idDaPágina}_{idDoPost}). |
| post.caption | string | não | Legenda, para mostrar no painel. |
| post.thumbnail_url | string | não | Imagem do post, para mostrar no painel. |
| post.permalink | string | não | Link público do post. |
| status | string | não | paused (padrão) ou active. |
| budget.type | string | não | daily (padrão) ou lifetime (exige schedule.end_date). |
| budget.amount_cents | integer | sim | Valor em centavos (respeita a mínima da conta). |
| schedule.start_date | string | não | AAAA-MM-DD no fuso da conta (padrão: hoje). |
| schedule.end_date | string | não | AAAA-MM-DD. |
| persona_id | integer | não | Persona como base do público. |
| targeting | object | não | Países, cidades (com radius_km), idade, gênero, interesses, grupos de interesses (narrow_interests) e audience_id, como em POST /campaigns. |
| link_url | string | não | Destino do botão (obrigatório em traffic). |
| call_to_action | string | não | Botão em traffic (padrão LEARN_MORE). |
| instagram_account_id | string | não | Perfil do Instagram com que o anúncio roda. |
| tracking.pixel_id | string | não | Pixel que marca "Eventos do site" no anúncio (GET /ad-account/pixels). Sem ele, o padrão da conta ou o único que ela enxerga, como em POST /campaigns. Na resposta, boost.tracking.pixel_id mostra o Pixel escolhido. |

```bash
curl -X POST "https://leadgo.com.br/api/v1/boosts" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Impulsionar | Coleção de verão",
    "goal": "engagement",
    "budget": {
        "amount_cents": 2000
    },
    "post": {
        "source": "instagram",
        "id": "17900000000000001"
    },
    "targeting": {
        "cities": [
            {
                "key": "269969",
                "name": "São Paulo, São Paulo",
                "country_code": "BR",
                "radius_km": 25
            }
        ],
        "age_min": 20,
        "age_max": 45
    }
}'
```

Resposta 202:

```json
{
    "data": {
        "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "name": "Impulsionar | Coleção de verão",
        "goal": "engagement",
        "goal_label": "Engajamento",
        "status": "creating",
        "status_label": "Publicando",
        "created_via": "api",
        "budget": {
            "level": "campaign",
            "type": "daily",
            "amount_cents": 5000,
            "currency": "BRL"
        },
        "schedule": {
            "starts_at": "2026-10-28T03:00:00+00:00",
            "ends_at": null
        },
        "error": null,
        "platform": "meta",
        "platform_campaign_id": null,
        "published_at": null,
        "synced_at": null,
        "created_at": "2026-10-28T15:00:00+00:00",
        "updated_at": "2026-10-28T15:02:11+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
        },
        "draft": null,
        "boost": {
            "name": "Impulsionar | Coleção de verão",
            "goal": "engagement",
            "status": "paused",
            "budget": {
                "type": "daily",
                "amount_cents": 2000
            },
            "post": {
                "source": "instagram",
                "id": "17900000000000001",
                "caption": null,
                "thumbnail_url": null,
                "permalink": null
            },
            "tracking": {
                "pixel_id": "123456789012345"
            }
        },
        "ad_sets": []
    }
}
```

Erros: `validation_failed`, `ad_account_not_linked`, `too_many_publishing`, `campaign_creation_limit`, `idempotency_key_reused`, `idempotency_in_progress`.

#### Consultar campanha

`GET /campaigns/{id}` · escopo `campaigns:read`

A campanha com os conjuntos, os anúncios (com a situação da revisão da Meta) e, se foi criada pelo LeadGo, o rascunho de criação em draft. Em status failed, o motivo vem em error.message.

```bash
curl -X GET "https://leadgo.com.br/api/v1/campaigns/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "name": "Promoção de outubro",
        "goal": "traffic",
        "goal_label": "Tráfego",
        "status": "pending_review",
        "status_label": "Em análise",
        "created_via": "api",
        "budget": {
            "level": "campaign",
            "type": "daily",
            "amount_cents": 5000,
            "currency": "BRL"
        },
        "schedule": {
            "starts_at": "2026-10-28T03:00:00+00:00",
            "ends_at": null
        },
        "error": null,
        "platform": "meta",
        "platform_campaign_id": "120212345678900001",
        "published_at": "2026-10-28T15:02:11+00:00",
        "synced_at": "2026-10-28T15:40:00+00:00",
        "created_at": "2026-10-28T15:00:00+00:00",
        "updated_at": "2026-10-28T15:02:11+00:00",
        "draft": {
            "name": "Promoção de outubro",
            "goal": "traffic",
            "status": "paused",
            "budget": {
                "type": "daily",
                "amount_cents": 5000
            },
            "schedule": {
                "start_date": "2026-10-28",
                "end_date": null
            },
            "tracking": {
                "pixel_id": "123456789012345",
                "conversion_event": null,
                "url_tags": [
                    {
                        "key": "utm_source",
                        "value": "facebook"
                    },
                    {
                        "key": "utm_medium",
                        "value": "paid"
                    },
                    {
                        "key": "utm_campaign",
                        "value": "{{campaign.name}}"
                    },
                    {
                        "key": "utm_content",
                        "value": "{{ad.name}}"
                    }
                ]
            },
            "special_ad_category": null,
            "targeting": {
                "countries": [
                    "BR"
                ],
                "cities": [],
                "age_min": 25,
                "age_max": 55,
                "gender": "all",
                "interests": [],
                "narrow_interests": [],
                "audience_id": null
            },
            "link_url": "https://loja.exemplo.com.br/outubro",
            "creatives": [
                {
                    "media_type": "image",
                    "headline": "Outono com 30% off",
                    "body": "Peças selecionadas com frete grátis até domingo.",
                    "call_to_action": "SHOP_NOW"
                }
            ],
            "whatsapp": null,
            "lead_form": null
        },
        "ad_sets": [
            {
                "id": 88,
                "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                "name": "Promoção de outubro - Público",
                "status": "pending_review",
                "budget": null,
                "optimization_goal": "LINK_CLICKS",
                "targeting": {
                    "geo_locations": {
                        "countries": [
                            "BR"
                        ]
                    },
                    "age_min": 25,
                    "age_max": 55
                },
                "platform_ad_set_id": "120212345678900002",
                "ads": [
                    {
                        "id": 140,
                        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                        "ad_set_id": 88,
                        "name": "Promoção de outubro - Anúncio 1",
                        "status": "pending_review",
                        "review_status": "PENDING_REVIEW",
                        "rejection_reason": null,
                        "platform_ad_id": "120212345678900003",
                        "creative": {
                            "headline": "Outono com 30% off",
                            "body": "Peças selecionadas com frete grátis até domingo.",
                            "description": null,
                            "call_to_action": "SHOP_NOW",
                            "link_url": "https://loja.exemplo.com.br/outubro",
                            "image_url": "https://cdn.zernio.com/u/outubro.jpg",
                            "video_id": null
                        },
                        "url_tags": null
                    }
                ]
            }
        ],
        "brain": {
            "mode": null,
            "effective_mode": "manual",
            "target_cpa_cents": null,
            "target_roas": null,
            "effective_target_cpa_cents": 3000,
            "effective_target_roas": null,
            "locked_until": "2026-10-31T15:02:11+00:00",
            "last_touched_at": null,
            "last_touched_by": null
        },
        "links": {
            "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
        }
    }
}
```

Erros: `not_found`.

#### Alterar campanha

`PATCH /campaigns/{id}` · escopo `campaigns:write`

Nome, orçamento e status (ligar ou pausar) de uma campanha publicada, aplicados nessa ordem na Meta. Se um falhar, details.applied diz o que já mudou. Cada mudança abre a janela de maturação do Gestor IA (ele não mexe na campanha por alguns dias). O bloco brain muda a regra do Gestor IA só desta campanha (vale também antes de publicar e exige, de quem criou o token, a permissão de configurar a IA); a regra atual vem em brain no GET /campaigns/{id}.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | não | Novo nome (até 120 caracteres). |
| budget.amount_cents | integer | não | Novo orçamento em centavos (respeita a verba mínima da conta). |
| status | string | não | active ou paused. |
| brain.mode | string | não | auto, manual ou off só nesta campanha; null volta a seguir a conta. O automático depende do plano. |
| brain.target_cpa_cents | integer | não | Meta de custo por resultado em centavos (null usa a da conta). |
| brain.target_roas | number | não | Meta de ROAS, de 0 a 1000 (null usa a da conta). |

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/campaigns/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "budget": {
        "amount_cents": 8000
    },
    "status": "active",
    "brain": {
        "mode": "manual",
        "target_cpa_cents": 2500
    }
}'
```

Resposta 200:

```json
{
    "data": {
        "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "name": "Promoção de outubro",
        "goal": "traffic",
        "goal_label": "Tráfego",
        "status": "active",
        "status_label": "Ativa",
        "created_via": "api",
        "budget": {
            "level": "campaign",
            "type": "daily",
            "amount_cents": 5000,
            "currency": "BRL"
        },
        "schedule": {
            "starts_at": "2026-10-28T03:00:00+00:00",
            "ends_at": null
        },
        "error": null,
        "platform": "meta",
        "platform_campaign_id": "120212345678900001",
        "published_at": "2026-10-28T15:02:11+00:00",
        "synced_at": "2026-10-28T15:40:00+00:00",
        "created_at": "2026-10-28T15:00:00+00:00",
        "updated_at": "2026-10-28T15:02:11+00:00",
        "draft": {
            "name": "Promoção de outubro",
            "goal": "traffic",
            "status": "paused",
            "budget": {
                "type": "daily",
                "amount_cents": 5000
            },
            "schedule": {
                "start_date": "2026-10-28",
                "end_date": null
            },
            "tracking": {
                "pixel_id": "123456789012345",
                "conversion_event": null,
                "url_tags": [
                    {
                        "key": "utm_source",
                        "value": "facebook"
                    },
                    {
                        "key": "utm_medium",
                        "value": "paid"
                    },
                    {
                        "key": "utm_campaign",
                        "value": "{{campaign.name}}"
                    },
                    {
                        "key": "utm_content",
                        "value": "{{ad.name}}"
                    }
                ]
            },
            "special_ad_category": null,
            "targeting": {
                "countries": [
                    "BR"
                ],
                "cities": [],
                "age_min": 25,
                "age_max": 55,
                "gender": "all",
                "interests": [],
                "narrow_interests": [],
                "audience_id": null
            },
            "link_url": "https://loja.exemplo.com.br/outubro",
            "creatives": [
                {
                    "media_type": "image",
                    "headline": "Outono com 30% off",
                    "body": "Peças selecionadas com frete grátis até domingo.",
                    "call_to_action": "SHOP_NOW"
                }
            ],
            "whatsapp": null,
            "lead_form": null
        },
        "ad_sets": [
            {
                "id": 88,
                "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                "name": "Promoção de outubro - Público",
                "status": "active",
                "budget": null,
                "optimization_goal": "LINK_CLICKS",
                "targeting": {
                    "geo_locations": {
                        "countries": [
                            "BR"
                        ]
                    },
                    "age_min": 25,
                    "age_max": 55
                },
                "platform_ad_set_id": "120212345678900002",
                "ads": [
                    {
                        "id": 140,
                        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                        "ad_set_id": 88,
                        "name": "Promoção de outubro - Anúncio 1",
                        "status": "active",
                        "review_status": "APPROVED",
                        "rejection_reason": null,
                        "platform_ad_id": "120212345678900003",
                        "creative": {
                            "headline": "Outono com 30% off",
                            "body": "Peças selecionadas com frete grátis até domingo.",
                            "description": null,
                            "call_to_action": "SHOP_NOW",
                            "link_url": "https://loja.exemplo.com.br/outubro",
                            "image_url": "https://cdn.zernio.com/u/outubro.jpg",
                            "video_id": null
                        },
                        "url_tags": null
                    }
                ]
            }
        ],
        "brain": {
            "mode": null,
            "effective_mode": "manual",
            "target_cpa_cents": null,
            "target_roas": null,
            "effective_target_cpa_cents": 3000,
            "effective_target_roas": null,
            "locked_until": "2026-10-31T15:02:11+00:00",
            "last_touched_at": null,
            "last_touched_by": null
        },
        "links": {
            "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
        }
    }
}
```

Erros: `validation_failed`, `permission_denied`, `campaign_not_published`, `campaign_not_toggleable`, `operation_failed`, `platform_rejected`.

#### Excluir campanha

`DELETE /campaigns/{id}` · escopo `campaigns:write`

Publicada: exclui de vez na Meta, com conjuntos e anúncios. Não publicada (falhou): descarta o rascunho. Durante a publicação, responde 409.

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/campaigns/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`, `campaign_publishing`, `platform_rejected`.

#### Reenviar publicação

`POST /campaigns/{id}/retry` · escopo `campaigns:write` · aceita `Idempotency-Key`

Tenta de novo publicar uma campanha com status failed, depois de corrigir o motivo (forma de pagamento, Página, conexão, Pixel...). As mídias que já subiram não sobem de novo. Uma campanha sem Pixel (publicada antes da 1.2.0) ganha o padrão da conta ou o único que ela enxerga; sem Pixel para escolher, o reenvio é recusado com operation_failed.

```bash
curl -X POST "https://leadgo.com.br/api/v1/campaigns/{id}/retry" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)"
```

Resposta 202:

```json
{
    "data": {
        "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "name": "Promoção de outubro",
        "goal": "traffic",
        "goal_label": "Tráfego",
        "status": "creating",
        "status_label": "Publicando",
        "created_via": "api",
        "budget": {
            "level": "campaign",
            "type": "daily",
            "amount_cents": 5000,
            "currency": "BRL"
        },
        "schedule": {
            "starts_at": "2026-10-28T03:00:00+00:00",
            "ends_at": null
        },
        "error": null,
        "platform": "meta",
        "platform_campaign_id": null,
        "published_at": null,
        "synced_at": null,
        "created_at": "2026-10-28T15:00:00+00:00",
        "updated_at": "2026-10-28T15:02:11+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
        }
    }
}
```

Erros: `not_found`, `operation_failed`.

#### Duplicar campanha

`POST /campaigns/{id}/duplicate` · escopo `campaigns:write` · aceita `Idempotency-Key`

Nova campanha igual a uma criada pelo LeadGo, trocando só o que vier no corpo (mesmos campos de POST /campaigns, todos opcionais). Mídias e formulário de cadastro são os da origem; o objetivo não muda. Saem todos os anúncios da origem, cada um com o tipo dele (arte, vídeo ou carrossel): os criativos se mesclam por posição e só os textos mudam. Envie [{"headline": "..."}] para trocar só o título do primeiro. media_id ou template_media_index num criativo ou num cartão é recusado (422); para outra mídia, crie a campanha em POST /campaigns. No carrossel, cards troca a lista inteira: envie todos os cartões, no mesmo número e na mesma ordem da origem (as imagens seguem as da origem). Num carrossel sozinho, os cartões ficam em carousel.cards, como no draft de GET /campaigns/{id}; creatives[0].cards também vale. Sem name, vira "Nome (cópia)".

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| validate_only | boolean | não | Só confere (200, nada é criado). |

```bash
curl -X POST "https://leadgo.com.br/api/v1/campaigns/{id}/duplicate" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "budget": {
        "amount_cents": 9000
    },
    "creatives": [
        {
            "headline": "Agora com 40% off"
        }
    ]
}'
```

Resposta 202:

```json
{
    "data": {
        "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "name": "Promoção de outubro",
        "goal": "traffic",
        "goal_label": "Tráfego",
        "status": "creating",
        "status_label": "Publicando",
        "created_via": "api",
        "budget": {
            "level": "campaign",
            "type": "daily",
            "amount_cents": 5000,
            "currency": "BRL"
        },
        "schedule": {
            "starts_at": "2026-10-28T03:00:00+00:00",
            "ends_at": null
        },
        "error": null,
        "platform": "meta",
        "platform_campaign_id": null,
        "published_at": null,
        "synced_at": null,
        "created_at": "2026-10-28T15:00:00+00:00",
        "updated_at": "2026-10-28T15:02:11+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
        }
    }
}
```

Erros: `validation_failed`, `campaign_not_duplicable`, `ad_account_changed`, `too_many_publishing`.

#### Criar campanha a partir de um padrão

`POST /templates/{id}/campaigns` · escopo `campaigns:write` · aceita `Idempotency-Key`

Vale a configuração do padrão e o corpo traz só o que muda (mesmos campos de POST /campaigns, todos opcionais). Sem creatives (ou com null), saem todos os anúncios do padrão. Com creatives, sai só o que veio: um anúncio por item, na ordem, e os anúncios do padrão que não vierem ficam de fora. Na posição de um anúncio do padrão, {} o mantém como está (textos e mídia guardada), um item só com textos troca os textos e mantém a mídia, e um item com media_id ou cards troca o anúncio, inclusive o tipo (arte, vídeo ou carrossel). Itens além dos do padrão viram anúncios a mais no mesmo conjunto: repetem o título, o texto, a descrição e o botão do primeiro (já com o que veio no corpo) e trazem a própria mídia. Num padrão de um anúncio, por exemplo, [{"media_id": "<foto>"}, {"media_id": "<vídeo>"}] publica a foto e o vídeo como dois anúncios com os mesmos textos; com só uma das duas, envie um item. Cada cartão de carrossel usa a imagem guardada no padrão (template_media_index) ou uma nova (media_id). creatives [] é recusado. Até 10 anúncios, um carrossel só; variações de texto valem num anúncio simples. O padrão só de carrossel aceita também o formato antigo (format carousel e carousel.cards). Com persona no padrão, o público vem dela; o público da conta (audience_id), as exclusões (excluded_cities, excluded_audience_ids), os idiomas e o Advantage+ seguem os guardados no padrão. persona_id null usa o público guardado no padrão. Com duração definida no padrão, a data de término é calculada.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| validate_only | boolean | não | Só confere (200, nada é criado). |

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| creatives | object[] | não | Os anúncios a publicar, na ordem. Ausente ou null: todos os do padrão. Com a lista, só os enviados ({} mantém o anúncio do padrão daquela posição). |
| creatives[].media_id | string | não | Arte ou vídeo novo da biblioteca para o anúncio dessa posição (troca o que o padrão tinha ali). Obrigatória nos itens além dos do padrão. |
| creatives[].template_media_index | integer | não | Arte ou vídeo guardado no padrão (template_media_index e has_media em creatives de GET /templates/{id}). |
| creatives[].cards | object[] | não | Carrossel nesta posição, com os mesmos campos de POST /campaigns (troca a lista inteira). Cada cartão leva media_id ou template_media_index. |
| creatives[].cards[].media_id | string | não | Imagem nova da biblioteca para o cartão. |
| creatives[].cards[].template_media_index | integer | não | Imagem guardada no padrão para o cartão (creatives[].cards em GET /templates/{id}). |

```bash
curl -X POST "https://leadgo.com.br/api/v1/templates/{id}/campaigns" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Oferta de novembro",
    "budget": {
        "amount_cents": 7000
    },
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht"
        },
        {
            "media_id": "01jbx3n2p6s0d4g8j2m6q0t4wy"
        },
        {
            "cards": [
                {
                    "media_id": "01jbx3p5r9t3e7h1k5n9r3v7c2",
                    "headline": "Vestidos",
                    "link_url": "https://loja.exemplo.com.br/verao/vestidos"
                },
                {
                    "media_id": "01jbx3q8v2w6a0c4f8j2m6p0s4",
                    "headline": "Sandálias",
                    "link_url": "https://loja.exemplo.com.br/verao/sandalias"
                }
            ]
        }
    ]
}'
```

Resposta 202:

```json
{
    "data": {
        "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "name": "Promoção de outubro",
        "goal": "traffic",
        "goal_label": "Tráfego",
        "status": "creating",
        "status_label": "Publicando",
        "created_via": "api",
        "budget": {
            "level": "campaign",
            "type": "daily",
            "amount_cents": 5000,
            "currency": "BRL"
        },
        "schedule": {
            "starts_at": "2026-10-28T03:00:00+00:00",
            "ends_at": null
        },
        "error": null,
        "platform": "meta",
        "platform_campaign_id": null,
        "published_at": null,
        "synced_at": null,
        "created_at": "2026-10-28T15:00:00+00:00",
        "updated_at": "2026-10-28T15:02:11+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/campaigns/01jby0q4c8m2d6t9v3k7w5r1xa"
        }
    }
}
```

Erros: `validation_failed`, `operation_failed`, `too_many_publishing`.

### Conjuntos e anúncios

Operar cada conjunto e cada anúncio publicado: verba, status, nome, troca do criativo e das UTMs, prévia.

#### Alterar conjunto

`PATCH /ad-sets/{id}` · escopo `campaigns:write`

Nome, orçamento e status (ligar ou pausar) de um conjunto, aplicados nessa ordem na Meta. O orçamento só muda aqui quando a verba é por conjunto; quando ela é da campanha, use PATCH /campaigns/{id}. Se um passo falhar, details.applied diz o que já mudou. Os ids dos conjuntos vêm em ad_sets de GET /campaigns/{id}.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | não | Novo nome (até 120 caracteres). |
| budget.amount_cents | integer | não | Novo orçamento do conjunto em centavos. |
| status | string | não | active ou paused. |

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/ad-sets/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "budget": {
        "amount_cents": 4000
    },
    "status": "active"
}'
```

Resposta 200:

```json
{
    "data": {
        "id": 88,
        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "name": "Mulheres 25-45 SP",
        "status": "active",
        "budget": {
            "type": "daily",
            "amount_cents": 4000
        },
        "optimization_goal": "LINK_CLICKS",
        "targeting": {
            "geo_locations": {
                "countries": [
                    "BR"
                ]
            },
            "age_min": 25,
            "age_max": 45
        },
        "platform_ad_set_id": "120212345678900002",
        "ads": [
            {
                "id": 140,
                "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                "ad_set_id": 88,
                "name": "Promoção de outubro - Anúncio 1",
                "status": "active",
                "review_status": "APPROVED",
                "rejection_reason": null,
                "platform_ad_id": "120212345678900003",
                "creative": {
                    "headline": "Outono com 30% off",
                    "body": "Peças selecionadas com frete grátis até domingo.",
                    "description": null,
                    "call_to_action": "SHOP_NOW",
                    "link_url": "https://loja.exemplo.com.br/outubro",
                    "image_url": "https://cdn.zernio.com/u/outubro.jpg",
                    "video_id": null
                },
                "url_tags": null
            }
        ]
    }
}
```

Erros: `validation_failed`, `not_found`, `not_toggleable`, `operation_failed`, `platform_rejected`.

#### Excluir conjunto

`DELETE /ad-sets/{id}` · escopo `campaigns:write`

Exclui de vez na Meta o conjunto e os anúncios dele. A campanha continua.

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/ad-sets/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`, `platform_rejected`.

#### Alterar anúncio

`PATCH /ads/{id}` · escopo `campaigns:write`

Nome, criativo, UTMs e status de um anúncio publicado. No criativo, só o que vier muda: a Meta preserva o resto (inclusive a mídia e as UTMs) e revisa o anúncio de novo (review_status volta a PENDING_REVIEW). Para trocar a imagem, envie antes a nova em POST /media e use o id em creative.media_id; anúncios de vídeo trocam só texto, link, botão e UTMs. Mudar o criativo abre a janela de maturação do Gestor IA.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | não | Novo nome. |
| status | string | não | active ou paused. |
| creative.headline | string | não | Título (até 255). |
| creative.body | string | não | Texto principal (até 2000). |
| creative.description | string | não | Descrição (até 255). |
| creative.link_url | string | não | Link https de destino. |
| creative.call_to_action | string | não | Botão (veja GET /reference). |
| creative.media_id | string | não | Imagem nova, pronta na biblioteca (POST /media). |
| url_tags | object[] | não | UTMs {key, value} do anúncio: a lista enviada substitui a toda ([] remove todas). Até 10 extras além dos 4 padrão (utm_source, utm_medium, utm_campaign e utm_content), que não contam: a lista de uma campanha (os 4 + até 10 extras) cabe. Aceita as macros da Meta, como {{campaign.name}} e {{ad.name}}. |

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/ads/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "creative": {
        "headline": "Últimas peças com 40% off",
        "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht"
    },
    "url_tags": [
        {
            "key": "utm_source",
            "value": "facebook"
        },
        {
            "key": "utm_medium",
            "value": "paid"
        },
        {
            "key": "utm_campaign",
            "value": "{{campaign.name}}"
        },
        {
            "key": "utm_content",
            "value": "{{ad.name}}"
        }
    ]
}'
```

Resposta 200:

```json
{
    "data": {
        "id": 140,
        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "ad_set_id": 88,
        "name": "Promoção de outubro - Anúncio 1",
        "status": "active",
        "review_status": "PENDING_REVIEW",
        "rejection_reason": null,
        "platform_ad_id": "120212345678900003",
        "creative": {
            "headline": "Outono com 30% off",
            "body": "Peças selecionadas com frete grátis até domingo.",
            "description": null,
            "call_to_action": "SHOP_NOW",
            "link_url": "https://loja.exemplo.com.br/outubro",
            "image_url": "https://cdn.zernio.com/u/outubro.jpg",
            "video_id": null
        },
        "url_tags": [
            {
                "key": "utm_source",
                "value": "facebook"
            },
            {
                "key": "utm_medium",
                "value": "paid"
            },
            {
                "key": "utm_campaign",
                "value": "{{campaign.name}}"
            },
            {
                "key": "utm_content",
                "value": "{{ad.name}}"
            }
        ]
    }
}
```

Erros: `validation_failed`, `not_found`, `ad_not_published`, `ad_media_not_supported`, `boost_creative_locked`, `not_toggleable`, `operation_failed`, `platform_rejected`.

#### Excluir anúncio

`DELETE /ads/{id}` · escopo `campaigns:write`

Exclui de vez o anúncio na Meta. O conjunto e a campanha continuam.

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/ads/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`, `platform_rejected`.

#### Prévia do anúncio

`GET /ads/{id}/preview` · escopo `campaigns:read`

Como o anúncio publicado aparece em cada posicionamento, gerado pela Meta. url é o endereço da prévia para abrir num <iframe> (expira em poucos minutos; não guarde); html é o trecho original da Meta. Formatos: MOBILE_FEED_STANDARD, INSTAGRAM_STANDARD, INSTAGRAM_STORY, INSTAGRAM_REELS, FACEBOOK_STORY_MOBILE, DESKTOP_FEED_STANDARD.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| formats | string | não | Formatos separados por vírgula (padrão: todos). |

```bash
curl -X GET "https://leadgo.com.br/api/v1/ads/{id}/preview?formats=INSTAGRAM_STORY%2CMOBILE_FEED_STANDARD" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "ad_id": 140,
        "previews": [
            {
                "format": "INSTAGRAM_STORY",
                "label": "Stories do Instagram",
                "url": "https://www.facebook.com/ads/api/preview_iframe.php?d=AQ...&t=AQ...",
                "html": "<iframe src=\"https://www.facebook.com/ads/api/preview_iframe.php?d=AQ...&t=AQ...\" width=\"274\" height=\"213\" scrolling=\"yes\" style=\"border: none;\"></iframe>"
            }
        ]
    }
}
```

Erros: `validation_failed`, `not_found`, `ad_not_published`, `platform_quota_exceeded`, `platform_unavailable`.

### Personas e padrões

Públicos salvos (à mão ou sugeridos pela IA) e padrões de campanha: criar, alterar, excluir e reaproveitar.

#### Listar personas

`GET /personas` · escopo `campaigns:read`

Públicos salvos da conta. Use o id em persona_id: a campanha herda idade, gênero, locais, interesses e grupos de interesses (o que vier em targeting sobrescreve, campo a campo).

```bash
curl -X GET "https://leadgo.com.br/api/v1/personas" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": 5,
            "name": "Mães que trabalham fora",
            "description": "Mulheres de 28 a 45 anos, com filhos pequenos, sem tempo para cozinhar.",
            "source": "manual",
            "targeting": {
                "countries": [
                    "BR"
                ],
                "cities": [],
                "age_min": 28,
                "age_max": 45,
                "gender": "female",
                "interests": [
                    {
                        "id": "6003107902433",
                        "name": "Maternidade"
                    }
                ],
                "narrow_interests": []
            },
            "created_at": "2026-10-10T12:00:00+00:00",
            "updated_at": "2026-10-10T12:00:00+00:00"
        }
    ]
}
```

#### Consultar persona

`GET /personas/{id}` · escopo `campaigns:read`

Uma persona da conta.

```bash
curl -X GET "https://leadgo.com.br/api/v1/personas/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": 5,
        "name": "Mães que trabalham fora",
        "description": "Mulheres de 28 a 45 anos, com filhos pequenos, sem tempo para cozinhar.",
        "source": "manual",
        "targeting": {
            "countries": [
                "BR"
            ],
            "cities": [],
            "age_min": 28,
            "age_max": 45,
            "gender": "female",
            "interests": [
                {
                    "id": "6003107902433",
                    "name": "Maternidade"
                }
            ],
            "narrow_interests": []
        },
        "created_at": "2026-10-10T12:00:00+00:00",
        "updated_at": "2026-10-10T12:00:00+00:00"
    }
}
```

Erros: `not_found`.

#### Criar persona

`POST /personas` · escopo `campaigns:write`

Salva um público para reaproveitar nas campanhas e nos padrões. Aparece também no painel, no passo Público dos assistentes.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | sim | Nome (até 120 caracteres). |
| description | string | não | Quem é esse cliente (até 2000). |
| source | string | não | manual (padrão) ou ai, quando vier de POST /personas/generate. |
| targeting.countries | string[] | não | Países (GET /reference). Sem país nem cidade, o Brasil. |
| targeting.cities | object[] | não | Cidades {key, name, country_code, radius_km} de GET /targeting/cities. |
| targeting.age_min | integer | não | De 13 a 65 (padrão 18). |
| targeting.age_max | integer | não | De 13 a 65 (padrão 65). |
| targeting.gender | string | não | all, male ou female. |
| targeting.interests | object[] | não | Interesses {id, name} de GET /targeting/interests: basta ter um deles (OU). |
| targeting.narrow_interests | object[][] | não | Grupos que o público também precisa ter: um interesse de cada grupo (E), além de um da lista principal (OU). Até 3 grupos. Na campanha, não combina com Advantage+ público. |

```bash
curl -X POST "https://leadgo.com.br/api/v1/personas" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mães que trabalham fora",
    "description": "Mulheres de 28 a 45 anos, com filhos pequenos, sem tempo para cozinhar.",
    "targeting": {
        "countries": [
            "BR"
        ],
        "age_min": 28,
        "age_max": 45,
        "gender": "female",
        "interests": [
            {
                "id": "6003107902433",
                "name": "Maternidade"
            }
        ]
    }
}'
```

Resposta 201:

```json
{
    "data": {
        "id": 5,
        "name": "Mães que trabalham fora",
        "description": "Mulheres de 28 a 45 anos, com filhos pequenos, sem tempo para cozinhar.",
        "source": "manual",
        "targeting": {
            "countries": [
                "BR"
            ],
            "cities": [],
            "age_min": 28,
            "age_max": 45,
            "gender": "female",
            "interests": [
                {
                    "id": "6003107902433",
                    "name": "Maternidade"
                }
            ],
            "narrow_interests": []
        },
        "created_at": "2026-10-10T12:00:00+00:00",
        "updated_at": "2026-10-10T12:00:00+00:00"
    }
}
```

Erros: `validation_failed`.

#### Alterar persona

`PATCH /personas/{id}` · escopo `campaigns:write`

Só o que vier muda; os campos de targeting enviados vão por cima dos atuais, um a um (enviar targeting.interests troca a lista toda; targeting.narrow_interests troca todos os grupos, e [] tira). Campanhas já criadas não mudam.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | não | Novo nome. |
| description | string | não | Nova descrição (null apaga). |
| targeting.countries | string[] | não | Países (GET /reference). Sem país nem cidade, o Brasil. |
| targeting.cities | object[] | não | Cidades {key, name, country_code, radius_km} de GET /targeting/cities. |
| targeting.age_min | integer | não | De 13 a 65 (padrão 18). |
| targeting.age_max | integer | não | De 13 a 65 (padrão 65). |
| targeting.gender | string | não | all, male ou female. |
| targeting.interests | object[] | não | Interesses {id, name} de GET /targeting/interests: basta ter um deles (OU). |
| targeting.narrow_interests | object[][] | não | Grupos que o público também precisa ter: um interesse de cada grupo (E), além de um da lista principal (OU). Até 3 grupos. Na campanha, não combina com Advantage+ público. |

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/personas/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "targeting": {
        "age_min": 30
    }
}'
```

Resposta 200:

```json
{
    "data": {
        "id": 5,
        "name": "Mães que trabalham fora",
        "description": "Mulheres de 28 a 45 anos, com filhos pequenos, sem tempo para cozinhar.",
        "source": "manual",
        "targeting": {
            "countries": [
                "BR"
            ],
            "cities": [],
            "age_min": 28,
            "age_max": 45,
            "gender": "female",
            "interests": [
                {
                    "id": "6003107902433",
                    "name": "Maternidade"
                }
            ],
            "narrow_interests": []
        },
        "created_at": "2026-10-10T12:00:00+00:00",
        "updated_at": "2026-10-10T12:00:00+00:00"
    }
}
```

Erros: `validation_failed`, `not_found`.

#### Excluir persona

`DELETE /personas/{id}` · escopo `campaigns:write`

Os padrões que usavam a persona passam a usar o público guardado neles; campanhas já criadas não mudam.

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/personas/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`.

#### Sugerir persona pela IA

`POST /personas/generate` · escopo `ai:draft`

O "Criar com a IA" das personas: a partir do que o negócio vende e para quem, a IA sugere nome, descrição, idade, gênero, interesses e, quando o público pede um cruzamento, grupos de interesses em narrow_interests (procurados na Meta; os que não acharmos voltam em unresolved_keywords). Nada é salvo: revise e envie em POST /personas. Leva de 10 a 40 segundos. Limite de 10 por hora por pessoa, somado ao painel.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| prompt | string | sim | O que o negócio vende e para quem (de 10 a 2000 caracteres). |

```bash
curl -X POST "https://leadgo.com.br/api/v1/personas/generate" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Marmitas fitness congeladas com entrega em São Paulo, para quem trabalha o dia todo e quer comer bem."
}'
```

Resposta 200:

```json
{
    "data": {
        "name": "Profissional sem tempo",
        "description": "Adultos de 25 a 45 anos que trabalham fora e cuidam da alimentação.",
        "source": "ai",
        "targeting": {
            "countries": [
                "BR"
            ],
            "cities": [],
            "age_min": 25,
            "age_max": 45,
            "gender": "all",
            "interests": [
                {
                    "id": "6003384248805",
                    "name": "Alimentação saudável"
                }
            ],
            "narrow_interests": []
        },
        "unresolved_keywords": [
            "marmita fit"
        ]
    }
}
```

Erros: `validation_failed`, `ai_rate_limited`, `ai_unavailable`, `platform_quota_exceeded`.

#### Listar padrões de campanha

`GET /templates` · escopo `campaigns:read`

Configurações prontas para reaproveitar (POST /templates/{id}/campaigns). Em creatives, um item por anúncio: has_media diz se o padrão já guarda a mídia daquele anúncio (no carrossel, de cada cartão, em cards).

```bash
curl -X GET "https://leadgo.com.br/api/v1/templates" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": 9,
            "name": "Oferta da semana",
            "description": null,
            "goal": "traffic",
            "persona_id": null,
            "duration_days": 7,
            "uses_count": 12,
            "last_used_at": "2026-10-27T10:00:00+00:00",
            "creatives": [
                {
                    "template_media_index": 0,
                    "media_type": "image",
                    "has_media": true
                },
                {
                    "media_type": "carousel",
                    "cards": [
                        {
                            "template_media_index": 1,
                            "has_media": true
                        },
                        {
                            "template_media_index": 2,
                            "has_media": true
                        }
                    ]
                }
            ],
            "has_lead_form": false,
            "draft": {
                "name": "Oferta da semana",
                "goal": "traffic",
                "budget": {
                    "type": "daily",
                    "amount_cents": 3000
                },
                "link_url": "https://loja.exemplo.com.br"
            },
            "created_at": "2026-10-01T12:00:00+00:00"
        }
    ]
}
```

#### Consultar padrão

`GET /templates/{id}` · escopo `campaigns:read`

Um padrão de campanha, com o rascunho completo em draft. Em creatives, um item por anúncio, na ordem: arte e vídeo trazem template_media_index, media_type e has_media; o carrossel traz media_type carousel e, em cards, o template_media_index e o has_media de cada cartão. Use esses índices em creatives[].template_media_index e creatives[].cards[].template_media_index.

```bash
curl -X GET "https://leadgo.com.br/api/v1/templates/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": 9,
        "name": "Oferta da semana",
        "description": null,
        "goal": "traffic",
        "persona_id": null,
        "duration_days": 7,
        "uses_count": 12,
        "last_used_at": "2026-10-27T10:00:00+00:00",
        "creatives": [
            {
                "template_media_index": 0,
                "media_type": "image",
                "has_media": true
            },
            {
                "media_type": "carousel",
                "cards": [
                    {
                        "template_media_index": 1,
                        "has_media": true
                    },
                    {
                        "template_media_index": 2,
                        "has_media": true
                    }
                ]
            }
        ],
        "has_lead_form": false,
        "draft": {
            "name": "Oferta da semana",
            "goal": "traffic",
            "budget": {
                "type": "daily",
                "amount_cents": 3000
            },
            "link_url": "https://loja.exemplo.com.br"
        },
        "created_at": "2026-10-01T12:00:00+00:00"
    }
}
```

Erros: `not_found`.

#### Criar padrão a partir de uma campanha

`POST /campaigns/{id}/template` · escopo `campaigns:write`

Congela a configuração da campanha (objetivo, verba, público, textos e mídias) para a próxima nascer igual. Vale para as criadas aqui e para as importadas da Meta (essas gastam a cota de consultas à plataforma). O público vira uma persona nova (new_persona), usa uma existente (existing_persona) ou fica só no padrão (inline, o padrão). Com replace_template_id, atualiza um padrão que já existe em vez de criar outro. Campanhas com carrossel viram padrão com a imagem de cada cartão; posts impulsionados, não.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | sim | Nome do padrão (até 120). |
| description | string | não | Até 500 caracteres. |
| audience.mode | string | não | new_persona, existing_persona ou inline (padrão). |
| audience.persona_name | string | não | Com new_persona: nome da persona criada. |
| audience.persona_id | integer | não | Com existing_persona: a persona a usar. |
| replace_template_id | integer | não | Padrão a atualizar (responde 200 em vez de 201). |

```bash
curl -X POST "https://leadgo.com.br/api/v1/campaigns/{id}/template" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Oferta da semana",
    "audience": {
        "mode": "new_persona",
        "persona_name": "Público da oferta da semana"
    }
}'
```

Resposta 201:

```json
{
    "data": {
        "id": 9,
        "name": "Oferta da semana",
        "description": null,
        "goal": "traffic",
        "persona_id": null,
        "duration_days": 7,
        "uses_count": 12,
        "last_used_at": "2026-10-27T10:00:00+00:00",
        "creatives": [
            {
                "template_media_index": 0,
                "media_type": "image",
                "has_media": true
            },
            {
                "media_type": "carousel",
                "cards": [
                    {
                        "template_media_index": 1,
                        "has_media": true
                    },
                    {
                        "template_media_index": 2,
                        "has_media": true
                    }
                ]
            }
        ],
        "has_lead_form": false,
        "draft": {
            "name": "Oferta da semana",
            "goal": "traffic",
            "budget": {
                "type": "daily",
                "amount_cents": 3000
            },
            "link_url": "https://loja.exemplo.com.br"
        },
        "created_at": "2026-10-01T12:00:00+00:00"
    }
}
```

Erros: `validation_failed`, `not_found`, `campaign_not_templatable`, `operation_failed`, `platform_quota_exceeded`, `platform_unavailable`.

#### Alterar padrão

`PATCH /templates/{id}` · escopo `campaigns:write`

Edita o padrão por completo, menos o objetivo e a conta de anúncio. Só com name, description e persona_id, troca esses campos sem conferir a configuração (persona_id null tira a persona: vale o público guardado no padrão). Com qualquer outro campo, aceita os mesmos de POST /templates/{id}/campaigns (verba, público, posicionamentos, lance, link, rastreamento, mensagem do WhatsApp, textos, mídias, carrossel e variações de texto), todos opcionais, mais duration_days, com a mesma junção: objetos se mesclam campo a campo, listas são trocadas inteiras e, em creatives, sai só o que veio ({} mantém o anúncio do padrão daquela posição; media_id ou cards trocam o anúncio, inclusive o tipo; cards troca a lista inteira). As regras são as de criar campanha, na conta de anúncio do padrão (verba mínima, Pixel, públicos), mas a mídia pode faltar: anúncio ou cartão sem mídia fica sem mídia guardada, e ela é pedida ao usar o padrão. A mídia enviada é conferida: media_id da biblioteca sobe para o armazenamento e vira mídia guardada, e template_media_index aponta uma que o padrão já tem. Para manter a imagem de um cartão ao reenviar cards, envie o template_media_index dele (GET /templates/{id}). Com persona, o público vem dela; de targeting, seguem do padrão audience_id, excluded_cities, excluded_audience_ids, languages e advantage_audience, e o público guardado no padrão fica como está (é o que vale com persona_id null). Quando o público tem grupos de interesses (os da persona ou os enviados), o Advantage+ guardado sai sozinho; advantage_audience true enviado junto com grupos é recusado (422). goal diferente do padrão é recusado (422): para outro objetivo, crie outro padrão. schedule, status, lead_form e lead_form_id não entram: o padrão não guarda datas (a data de término sai de duration_days) e o formulário de cadastro segue o dele. Com orçamento total (lifetime), duration_days é obrigatório quando o padrão não tem duração. Com ?validate_only=true, confere do mesmo jeito e nada é gravado nem enviado ao armazenamento: a resposta é 200 com {"data": {"valid": true, "template": ...}}, o padrão como ficaria (a mídia da biblioteca aparece com has_media true). As campanhas já criadas com o padrão não mudam.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| validate_only | boolean | não | Só confere (200 com valid e o padrão como ficaria; nada é gravado e nenhuma mídia sobe). |

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| name | string | não | Novo nome. |
| description | string | não | Nova descrição (null apaga). |
| persona_id | integer | não | Persona do padrão (null tira). |
| duration_days | integer | não | Duração das campanhas criadas com o padrão, de 1 a 365 dias (null: sem data de término). |
| budget | object | não | type e amount_cents, como em POST /campaigns (verba mínima da conta do padrão). |
| targeting | object | não | Público, como em POST /campaigns (os campos enviados trocam os do padrão). |
| link_url | string | não | Link de destino. |
| tracking.url_tags | object[] | não | Parâmetros de URL extras {key, value}, somados aos 4 UTMs padrão ([] = nenhum extra). |
| creatives | object[] | não | Os anúncios do padrão, na ordem, como em POST /templates/{id}/campaigns. Ausente ou null: ficam como estão. Item sem mídia é aceito. |
| creatives[].media_id | string | não | Arte ou vídeo da biblioteca que passa a ser a mídia guardada do anúncio. |
| creatives[].template_media_index | integer | não | Mídia que o padrão já guarda (GET /templates/{id}), inclusive a de outra posição. |
| creatives[].cards | object[] | não | Os cartões do carrossel (troca a lista inteira, de 2 a 10). |
| creatives[].cards[].media_id | string | não | Imagem da biblioteca que passa a ser a imagem guardada do cartão. |
| creatives[].cards[].template_media_index | integer | não | Imagem guardada do cartão. Sem ela (nem media_id), o cartão fica sem imagem. |

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/templates/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "budget": {
        "amount_cents": 6000
    },
    "duration_days": 14,
    "tracking": {
        "url_tags": [
            {
                "key": "utm_term",
                "value": "inverno"
            }
        ]
    },
    "creatives": [
        {
            "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
            "headline": "Coleção de inverno"
        }
    ]
}'
```

Resposta 200:

```json
{
    "data": {
        "id": 9,
        "name": "Oferta da semana",
        "description": null,
        "goal": "traffic",
        "persona_id": null,
        "duration_days": 7,
        "uses_count": 12,
        "last_used_at": "2026-10-27T10:00:00+00:00",
        "creatives": [
            {
                "template_media_index": 0,
                "media_type": "image",
                "has_media": true
            },
            {
                "media_type": "carousel",
                "cards": [
                    {
                        "template_media_index": 1,
                        "has_media": true
                    },
                    {
                        "template_media_index": 2,
                        "has_media": true
                    }
                ]
            }
        ],
        "has_lead_form": false,
        "draft": {
            "name": "Oferta da semana",
            "goal": "traffic",
            "budget": {
                "type": "daily",
                "amount_cents": 3000
            },
            "link_url": "https://loja.exemplo.com.br"
        },
        "created_at": "2026-10-01T12:00:00+00:00"
    }
}
```

Erros: `validation_failed`, `not_found`, `operation_failed`, `platform_quota_exceeded`, `platform_rejected`, `platform_unavailable`, `platform_rate_limited`.

#### Excluir padrão

`DELETE /templates/{id}` · escopo `campaigns:write`

As campanhas criadas com o padrão não mudam.

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/templates/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`.

### Públicos

Remarketing (visitantes do site, engajamento com a Página e o Instagram), compradores e os públicos que já existem na Meta, para incluir ou excluir na segmentação.

#### Listar públicos

`GET /audiences` · escopo `campaigns:read`

Os públicos da conta de anúncio: os do sistema (source system: compradores e o semelhante a eles), os de remarketing criados pela API ou pelo painel (source user) e os que já existiam na Meta (source imported, trazidos por POST /audiences/sync e todo dia). Use o platform_audience_id em targeting.audience_id (incluir) ou targeting.excluded_audience_ids (deixar de fora).

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| type | string | não | customer_list, lookalike, website ou meta_engagement. |
| source | string | não | system, user ou imported. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/audiences" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": 17,
            "platform_audience_id": "23859876543210987",
            "name": "Visitantes do site — 30 dias",
            "description": null,
            "type": "website",
            "type_label": "Visitantes do site",
            "source": "user",
            "purpose": null,
            "status": "ready",
            "rule": {
                "pixel_id": "123456789012345",
                "retention_days": 30,
                "url_contains": "/produtos"
            },
            "country": null,
            "approximate_size": 4800,
            "can_delete": true,
            "last_synced_at": "2026-10-28T15:00:00+00:00",
            "created_at": "2026-10-28T15:00:00+00:00"
        },
        {
            "id": 3,
            "platform_audience_id": "23851234567890123",
            "name": "Compradores",
            "description": null,
            "type": "customer_list",
            "type_label": "Lista de clientes",
            "source": "system",
            "purpose": "buyers",
            "status": null,
            "rule": null,
            "country": null,
            "approximate_size": 1200,
            "can_delete": false,
            "last_synced_at": "2026-10-28T03:00:00+00:00",
            "created_at": "2026-10-01T03:00:00+00:00"
        }
    ]
}
```

Erros: `validation_failed`, `ad_account_not_linked`.

#### Consultar público

`GET /audiences/{id}` · escopo `campaigns:read`

Um público pelo id. approximate_size e status são os da última sincronização (a Meta leva algumas horas para encher um público novo e só entrega anúncios para ele a partir de cerca de 100 pessoas).

```bash
curl -X GET "https://leadgo.com.br/api/v1/audiences/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": 17,
        "platform_audience_id": "23859876543210987",
        "name": "Visitantes do site — 30 dias",
        "description": null,
        "type": "website",
        "type_label": "Visitantes do site",
        "source": "user",
        "purpose": null,
        "status": "ready",
        "rule": {
            "pixel_id": "123456789012345",
            "retention_days": 30,
            "url_contains": "/produtos"
        },
        "country": null,
        "approximate_size": 4800,
        "can_delete": true,
        "last_synced_at": "2026-10-28T15:00:00+00:00",
        "created_at": "2026-10-28T15:00:00+00:00"
    }
}
```

Erros: `not_found`.

#### Criar público de remarketing

`POST /audiences` · escopo `campaigns:write`

Cria na Meta um público de quem visitou o site (type website, pelo Pixel) ou de quem interagiu com a Página ou o perfil do Instagram (type meta_engagement). A pessoa fica no público pelos dias de retention_days depois da última visita ou interação. Gasta a cota de consultas à plataforma.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| type | string | sim | website ou meta_engagement. |
| name | string | sim | Nome do público (até 120 caracteres). |
| description | string | não | Para você lembrar do que se trata (até 500). |
| retention_days | integer | sim | Dias para trás: até 180 no website e até 365 no engajamento. |
| pixel_id | string | não | website: o Pixel (GET /ad-account/pixels). Obrigatório nesse tipo. |
| url_contains | string | não | website: só quem visitou páginas cujo endereço contém esse trecho (ex.: /produtos). Em branco, qualquer página. |
| engagement_source | string | não | meta_engagement: page (Página do Facebook) ou instagram. Obrigatório nesse tipo. |
| source_id | string | não | meta_engagement: id da Página ou do perfil do Instagram (GET /ad-account/instagram-accounts). Em branco com page, vale a Página da conexão; com instagram é obrigatório. |

```bash
curl -X POST "https://leadgo.com.br/api/v1/audiences" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "website",
    "name": "Visitantes do site — 30 dias",
    "retention_days": 30,
    "pixel_id": "123456789012345",
    "url_contains": "/produtos"
}'
```

Resposta 201:

```json
{
    "data": {
        "id": 17,
        "platform_audience_id": "23859876543210987",
        "name": "Visitantes do site — 30 dias",
        "description": null,
        "type": "website",
        "type_label": "Visitantes do site",
        "source": "user",
        "purpose": null,
        "status": "ready",
        "rule": {
            "pixel_id": "123456789012345",
            "retention_days": 30,
            "url_contains": "/produtos"
        },
        "country": null,
        "approximate_size": 4800,
        "can_delete": true,
        "last_synced_at": "2026-10-28T15:00:00+00:00",
        "created_at": "2026-10-28T15:00:00+00:00"
    }
}
```

Erros: `validation_failed`, `ad_account_not_linked`, `operation_failed`, `platform_quota_exceeded`, `platform_rejected`, `platform_unavailable`.

#### Excluir público

`DELETE /audiences/{id}` · escopo `campaigns:write`

Exclui o público na Meta e aqui. Só os que têm can_delete true: os do sistema são mantidos pelas vendas dos leads, e os criados direto na Meta se excluem pelo Gerenciador de Anúncios. Campanhas que usavam o público perdem essa parte da segmentação.

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/audiences/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`, `audience_not_deletable`, `platform_quota_exceeded`, `platform_rejected`.

#### Sincronizar públicos com a Meta

`POST /audiences/sync` · escopo `campaigns:write`

Traz na hora a lista da conta de anúncio: os criados direto na Meta entram como imported, os conhecidos atualizam tamanho e situação, e os que sumiram de lá saem daqui. As rotinas já fazem isso todo dia; aqui, no máximo uma vez por minuto.

```bash
curl -X POST "https://leadgo.com.br/api/v1/audiences/sync" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "created": 2,
        "updated": 5,
        "removed": 0
    }
}
```

Erros: `ad_account_not_linked`, `sync_too_soon`, `operation_failed`, `platform_quota_exceeded`, `platform_unavailable`.

### Métricas e relatórios

Resultados da conta, de cada campanha e de cada anúncio, lidos da Meta (atualizam a cada 15–60 min).

#### Métricas da campanha

`GET /campaigns/{id}/metrics` · escopo `reports:read`

Resumo, série diária e quebras (idade, gênero, plataforma) do período, no fuso da conta, mais o resultado de cada conjunto (ad_sets) e de cada anúncio (ads), para comparar criativos. Valores de dinheiro na moeda da conta, em unidades (312.5 = R$ 312,50). results segue o objetivo (compras, leads do formulário, conversas do WhatsApp...). Até 90 dias por consulta; sem datas, os últimos 7 dias. Cache de 10 minutos.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| from | string | não | Data inicial (AAAA-MM-DD). |
| to | string | não | Data final (AAAA-MM-DD), inclusive. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/campaigns/{id}/metrics?from=2026-10-01&to=2026-10-27" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "from": "2026-10-01",
        "to": "2026-10-27",
        "currency": "BRL",
        "result_goal": "conversions",
        "summary": {
            "spend": 312.5,
            "impressions": 48000,
            "reach": 21000,
            "clicks": 960,
            "results": 24,
            "purchase_value": 1890,
            "ctr": 2,
            "cpc": 0.3255,
            "cpm": 6.5104,
            "cost_per_result": 13.0208,
            "roas": 6.048
        },
        "daily": [
            {
                "date": "2026-10-27",
                "spend": 12.3,
                "impressions": 1900,
                "reach": 1500,
                "clicks": 41,
                "results": 1,
                "purchase_value": 89.9,
                "ctr": 2.1579,
                "cpc": 0.3,
                "cpm": 6.4737,
                "cost_per_result": 12.3,
                "roas": 7.309
            }
        ],
        "breakdowns": {
            "age": [
                {
                    "age": "25-34",
                    "spend": 200
                }
            ]
        },
        "backfill_pending": false,
        "ad_sets": [
            {
                "id": 88,
                "name": "Mulheres 25-45 SP",
                "spend": 312.5,
                "impressions": 48000,
                "reach": 21000,
                "clicks": 960,
                "results": 24,
                "purchase_value": 1890,
                "ctr": 2,
                "cpc": 0.3255,
                "cpm": 6.5104,
                "cost_per_result": 13.0208,
                "roas": 6.048
            }
        ],
        "ads": [
            {
                "id": 140,
                "ad_set_id": 88,
                "name": "Promoção de outubro - Anúncio 1",
                "spend": 312.5,
                "impressions": 48000,
                "reach": 21000,
                "clicks": 960,
                "results": 24,
                "purchase_value": 1890,
                "ctr": 2,
                "cpc": 0.3255,
                "cpm": 6.5104,
                "cost_per_result": 13.0208,
                "roas": 6.048
            }
        ]
    }
}
```

Erros: `validation_failed`, `campaign_not_published`, `platform_quota_exceeded`, `platform_unavailable`.

#### Métricas do anúncio

`GET /ads/{id}/metrics` · escopo `reports:read`

Resumo, série diária e quebras de um anúncio, no mesmo formato das métricas da campanha (results segue o objetivo dela). Até 90 dias por consulta; sem datas, os últimos 7 dias. Cache de 10 minutos.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| from | string | não | Data inicial (AAAA-MM-DD). |
| to | string | não | Data final (AAAA-MM-DD), inclusive. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/ads/{id}/metrics?from=2026-10-01&to=2026-10-27" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "ad_id": 140,
        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "from": "2026-10-01",
        "to": "2026-10-27",
        "currency": "BRL",
        "result_goal": "conversions",
        "summary": {
            "spend": 312.5,
            "impressions": 48000,
            "reach": 21000,
            "clicks": 960,
            "results": 24,
            "purchase_value": 1890,
            "ctr": 2,
            "cpc": 0.3255,
            "cpm": 6.5104,
            "cost_per_result": 13.0208,
            "roas": 6.048
        },
        "daily": [
            {
                "date": "2026-10-27",
                "spend": 12.3,
                "impressions": 1900,
                "reach": 1500,
                "clicks": 41,
                "results": 1,
                "purchase_value": 89.9,
                "ctr": 2.1579,
                "cpc": 0.3,
                "cpm": 6.4737,
                "cost_per_result": 12.3,
                "roas": 7.309
            }
        ],
        "breakdowns": {
            "publisher_platform": [
                {
                    "publisher_platform": "instagram",
                    "spend": 180
                }
            ]
        },
        "backfill_pending": false
    }
}
```

Erros: `validation_failed`, `not_found`, `ad_not_published`, `platform_quota_exceeded`, `platform_unavailable`.

#### Relatório da conta

`GET /reports/account` · escopo `reports:read`

Totais e série diária da conta de anúncio inteira (todas as campanhas, inclusive as criadas fora do LeadGo), como na tela Relatórios do painel. results soma os resultados de cada campanha pelo objetivo dela. Até 90 dias por consulta; sem datas, os últimos 7 dias. Cache de 10 minutos.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| from | string | não | Data inicial (AAAA-MM-DD). |
| to | string | não | Data final (AAAA-MM-DD), inclusive. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/reports/account?from=2026-10-01&to=2026-10-27" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "period": {
            "from": "2026-10-01",
            "to": "2026-10-27"
        },
        "currency": "BRL",
        "summary": {
            "spend": 312.5,
            "impressions": 48000,
            "reach": 21000,
            "clicks": 960,
            "results": 24,
            "purchase_value": 1890,
            "ctr": 2,
            "cpc": 0.3255,
            "cpm": 6.5104,
            "cost_per_result": 13.0208,
            "roas": 6.048
        },
        "daily": [
            {
                "date": "2026-10-27",
                "spend": 12.3,
                "impressions": 1900,
                "reach": 1500,
                "clicks": 41,
                "results": 1,
                "purchase_value": 89.9,
                "ctr": 2.1579,
                "cpc": 0.3,
                "cpm": 6.4737,
                "cost_per_result": 12.3,
                "roas": 7.309
            }
        ],
        "backfill_pending": false
    }
}
```

Erros: `validation_failed`, `ad_account_not_linked`, `platform_quota_exceeded`, `platform_unavailable`.

#### Resultado por campanha

`GET /reports/campaigns` · escopo `reports:read`

Uma linha por campanha que rodou no período, do maior gasto para o menor, com o id daqui (campaign_id) para cruzar com GET /campaigns. Campanhas criadas fora do LeadGo e ainda não sincronizadas vêm com campaign_id null. Até 90 dias por consulta; sem datas, os últimos 7 dias. Cache de 10 minutos.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| from | string | não | Data inicial (AAAA-MM-DD). |
| to | string | não | Data final (AAAA-MM-DD), inclusive. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/reports/campaigns?from=2026-10-01&to=2026-10-27" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "period": {
            "from": "2026-10-01",
            "to": "2026-10-27"
        },
        "currency": "BRL",
        "campaigns": [
            {
                "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                "platform_campaign_id": "120212345678900001",
                "name": "Promoção de outubro",
                "status": "active",
                "metrics": {
                    "spend": 312.5,
                    "impressions": 48000,
                    "reach": 21000,
                    "clicks": 960,
                    "results": 24,
                    "purchase_value": 1890,
                    "ctr": 2,
                    "cpc": 0.3255,
                    "cpm": 6.5104,
                    "cost_per_result": 13.0208,
                    "roas": 6.048
                }
            }
        ],
        "backfill_pending": false
    }
}
```

Erros: `validation_failed`, `ad_account_not_linked`, `platform_quota_exceeded`, `platform_unavailable`.

### Leads

Leads dos formulários instantâneos e o funil comercial: listar, consultar, mudar a etapa, registrar venda e perda, notas e exclusão (LGPD).

#### Listar leads

`GET /leads` · escopo `leads:read`

Leads dos formulários instantâneos, dos mais novos para os mais antigos, com as respostas, a nota da IA e a campanha de origem. Pagine com cursor (meta.next_cursor). Para manter um CRM em dia, prefira os webhooks lead.created, lead.scored e lead.status_changed e use esta rota para a carga inicial ou para conferir. Dados pessoais: guarde só o que precisar.

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| status | string | não | new, contacted, qualified, won, lost ou spam. |
| campaign_id | string | não | Só os leads de uma campanha. |
| score | string | não | Nota da IA: hot, warm ou cold. |
| received_from | string | não | Recebidos a partir deste dia (AAAA-MM-DD, no fuso da conta). |
| received_to | string | não | Recebidos até este dia, inclusive (AAAA-MM-DD). |
| search | string | não | Nome, e-mail ou parte do telefone (4 dígitos ou mais). |
| limit | integer | não | Itens por página, de 1 a 100 (padrão 25). |
| cursor | string | não | meta.next_cursor da página anterior. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/leads" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "01jbz2k8m4q0t6w2y8b4e0h6na",
            "platform_lead_id": "1234567890123456",
            "name": "Mariana Lima",
            "email": "mariana@exemplo.com.br",
            "phone": "+5511987654321",
            "answers": {
                "Qual o melhor horário para contato?": "Depois das 18h",
                "Qual produto você procura?": "Kit verão"
            },
            "status": "new",
            "status_label": "Novo",
            "value_cents": null,
            "lost_reason": null,
            "score": "hot",
            "score_label": "Quente",
            "score_reason": "Respondeu o produto e o horário; quer comprar já.",
            "suggested_message": "Oi, Mariana! Vi que você procura o Kit verão. Posso te mandar as opções depois das 18h?",
            "whatsapp_url": "https://wa.me/5511987654321?text=Oi%2C%20Mariana%21",
            "campaign": {
                "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                "name": "Cadastro kit verão"
            },
            "ad_set": {
                "id": 88,
                "name": "Mulheres 25-45 SP"
            },
            "ad": {
                "id": 140,
                "name": "Anúncio 1"
            },
            "form_id": "987654321098765",
            "received_at": "2026-10-28T14:20:00+00:00",
            "first_contact_at": null,
            "qualified_at": null,
            "won_at": null,
            "scored_at": "2026-10-28T14:20:09+00:00",
            "links": {
                "panel": "https://app.leadgo.com.br/app/leads/01jbz2k8m4q0t6w2y8b4e0h6na"
            }
        }
    ],
    "links": {
        "first": null,
        "last": null,
        "prev": null,
        "next": "https://app.leadgo.com.br/api/v1/leads?cursor=eyJpZCI6..."
    },
    "meta": {
        "path": "https://app.leadgo.com.br/api/v1/leads",
        "per_page": 25,
        "next_cursor": "eyJpZCI6...",
        "prev_cursor": null
    }
}
```

Erros: `validation_failed`.

#### Indicadores do funil

`GET /leads/stats` · escopo `leads:read`

Os mesmos números da central de leads do painel no período: leads (sem spam), qualificados, vendas, faturamento em centavos, taxa de qualificação (%), minutos até o primeiro contato e custo por qualificado (na moeda da conta, com o gasto das campanhas de origem).

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| days | integer | não | 7, 30 (padrão), 90 ou 0 (todo o período). |

```bash
curl -X GET "https://leadgo.com.br/api/v1/leads/stats" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "days": 30,
        "today": 4,
        "total": 182,
        "qualified": 61,
        "won": 23,
        "revenue_cents": 1873400,
        "qualification_rate": 41.2,
        "avg_first_contact_minutes": 37,
        "cost_per_qualified": 18.4,
        "currency": "BRL"
    }
}
```

Erros: `validation_failed`.

#### Consultar lead

`GET /leads/{id}` · escopo `leads:read`

Um lead com o histórico (events, do mais novo ao mais antigo): recebido, nota da IA, mudanças de etapa, notas, contatos e envios ao CRM e à Meta.

```bash
curl -X GET "https://leadgo.com.br/api/v1/leads/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz2k8m4q0t6w2y8b4e0h6na",
        "platform_lead_id": "1234567890123456",
        "name": "Mariana Lima",
        "email": "mariana@exemplo.com.br",
        "phone": "+5511987654321",
        "answers": {
            "Qual o melhor horário para contato?": "Depois das 18h",
            "Qual produto você procura?": "Kit verão"
        },
        "status": "won",
        "status_label": "Vendeu",
        "value_cents": 25900,
        "lost_reason": null,
        "score": "hot",
        "score_label": "Quente",
        "score_reason": "Respondeu o produto e o horário; quer comprar já.",
        "suggested_message": "Oi, Mariana! Vi que você procura o Kit verão. Posso te mandar as opções depois das 18h?",
        "whatsapp_url": "https://wa.me/5511987654321?text=Oi%2C%20Mariana%21",
        "campaign": {
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Cadastro kit verão"
        },
        "ad_set": {
            "id": 88,
            "name": "Mulheres 25-45 SP"
        },
        "ad": {
            "id": 140,
            "name": "Anúncio 1"
        },
        "form_id": "987654321098765",
        "received_at": "2026-10-28T14:20:00+00:00",
        "first_contact_at": "2026-10-28T14:35:00+00:00",
        "qualified_at": "2026-10-28T16:00:00+00:00",
        "won_at": "2026-10-28T16:00:00+00:00",
        "scored_at": "2026-10-28T14:20:09+00:00",
        "events": [
            {
                "id": 902,
                "type": "status_changed",
                "label": "Status alterado",
                "data": {
                    "from": "contacted",
                    "to": "won",
                    "value_cents": 25900
                },
                "user": {
                    "id": 42,
                    "name": "Ana Souza"
                },
                "created_at": "2026-10-28T16:00:00+00:00"
            },
            {
                "id": 901,
                "type": "scored",
                "label": "Qualificado pela IA",
                "data": {
                    "score": "hot"
                },
                "user": null,
                "created_at": "2026-10-28T14:20:09+00:00"
            },
            {
                "id": 900,
                "type": "received",
                "label": "Lead recebido",
                "data": null,
                "user": null,
                "created_at": "2026-10-28T14:20:01+00:00"
            }
        ],
        "links": {
            "panel": "https://app.leadgo.com.br/app/leads/01jbz2k8m4q0t6w2y8b4e0h6na"
        }
    }
}
```

Erros: `not_found`.

#### Mudar a etapa do lead

`PATCH /leads/{id}` · escopo `leads:write`

Move o lead no funil, como no painel. A mudança entra no histórico, dispara o webhook lead.status_changed e, nos planos com inteligência de leads, manda a qualificação e a venda para a Meta (a Meta passa a buscar mais gente parecida com quem compra). Venda (won) pode levar o valor; perda (lost), o motivo.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| status | string | sim | new, contacted, qualified, won, lost ou spam. |
| value_cents | integer | não | Só com won: valor da venda em centavos (maior que zero). |
| lost_reason | string | não | Só com lost: motivo (até 255 caracteres). |

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/leads/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "won",
    "value_cents": 25900
}'
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz2k8m4q0t6w2y8b4e0h6na",
        "platform_lead_id": "1234567890123456",
        "name": "Mariana Lima",
        "email": "mariana@exemplo.com.br",
        "phone": "+5511987654321",
        "answers": {
            "Qual o melhor horário para contato?": "Depois das 18h",
            "Qual produto você procura?": "Kit verão"
        },
        "status": "won",
        "status_label": "Vendeu",
        "value_cents": 25900,
        "lost_reason": null,
        "score": "hot",
        "score_label": "Quente",
        "score_reason": "Respondeu o produto e o horário; quer comprar já.",
        "suggested_message": "Oi, Mariana! Vi que você procura o Kit verão. Posso te mandar as opções depois das 18h?",
        "whatsapp_url": "https://wa.me/5511987654321?text=Oi%2C%20Mariana%21",
        "campaign": {
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Cadastro kit verão"
        },
        "ad_set": {
            "id": 88,
            "name": "Mulheres 25-45 SP"
        },
        "ad": {
            "id": 140,
            "name": "Anúncio 1"
        },
        "form_id": "987654321098765",
        "received_at": "2026-10-28T14:20:00+00:00",
        "first_contact_at": "2026-10-28T14:35:00+00:00",
        "qualified_at": "2026-10-28T16:00:00+00:00",
        "won_at": "2026-10-28T16:00:00+00:00",
        "scored_at": "2026-10-28T14:20:09+00:00",
        "events": [
            {
                "id": 902,
                "type": "status_changed",
                "label": "Status alterado",
                "data": {
                    "from": "contacted",
                    "to": "won",
                    "value_cents": 25900
                },
                "user": {
                    "id": 42,
                    "name": "Ana Souza"
                },
                "created_at": "2026-10-28T16:00:00+00:00"
            },
            {
                "id": 901,
                "type": "scored",
                "label": "Qualificado pela IA",
                "data": {
                    "score": "hot"
                },
                "user": null,
                "created_at": "2026-10-28T14:20:09+00:00"
            },
            {
                "id": 900,
                "type": "received",
                "label": "Lead recebido",
                "data": null,
                "user": null,
                "created_at": "2026-10-28T14:20:01+00:00"
            }
        ],
        "links": {
            "panel": "https://app.leadgo.com.br/app/leads/01jbz2k8m4q0t6w2y8b4e0h6na"
        }
    }
}
```

Erros: `validation_failed`, `not_found`.

#### Registrar contato

`POST /leads/{id}/contacted` · escopo `leads:write`

O vendedor falou com o lead: grava o primeiro contato (que entra no tempo médio de resposta) e, se o lead ainda era new, passa para contacted.

```bash
curl -X POST "https://leadgo.com.br/api/v1/leads/{id}/contacted" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz2k8m4q0t6w2y8b4e0h6na",
        "platform_lead_id": "1234567890123456",
        "name": "Mariana Lima",
        "email": "mariana@exemplo.com.br",
        "phone": "+5511987654321",
        "answers": {
            "Qual o melhor horário para contato?": "Depois das 18h",
            "Qual produto você procura?": "Kit verão"
        },
        "status": "contacted",
        "status_label": "Contatado",
        "value_cents": null,
        "lost_reason": null,
        "score": "hot",
        "score_label": "Quente",
        "score_reason": "Respondeu o produto e o horário; quer comprar já.",
        "suggested_message": "Oi, Mariana! Vi que você procura o Kit verão. Posso te mandar as opções depois das 18h?",
        "whatsapp_url": "https://wa.me/5511987654321?text=Oi%2C%20Mariana%21",
        "campaign": {
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Cadastro kit verão"
        },
        "ad_set": {
            "id": 88,
            "name": "Mulheres 25-45 SP"
        },
        "ad": {
            "id": 140,
            "name": "Anúncio 1"
        },
        "form_id": "987654321098765",
        "received_at": "2026-10-28T14:20:00+00:00",
        "first_contact_at": "2026-10-28T14:35:00+00:00",
        "qualified_at": null,
        "won_at": null,
        "scored_at": "2026-10-28T14:20:09+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/leads/01jbz2k8m4q0t6w2y8b4e0h6na"
        }
    }
}
```

Erros: `not_found`.

#### Adicionar nota

`POST /leads/{id}/notes` · escopo `leads:write`

Uma anotação no histórico do lead, em nome de quem criou o token.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| text | string | sim | Até 2000 caracteres. |

```bash
curl -X POST "https://leadgo.com.br/api/v1/leads/{id}/notes" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Pediu orçamento do kit com 3 peças. Retornar na sexta."
}'
```

Resposta 201:

```json
{
    "data": {
        "id": "01jbz2k8m4q0t6w2y8b4e0h6na",
        "platform_lead_id": "1234567890123456",
        "name": "Mariana Lima",
        "email": "mariana@exemplo.com.br",
        "phone": "+5511987654321",
        "answers": {
            "Qual o melhor horário para contato?": "Depois das 18h",
            "Qual produto você procura?": "Kit verão"
        },
        "status": "contacted",
        "status_label": "Contatado",
        "value_cents": null,
        "lost_reason": null,
        "score": "hot",
        "score_label": "Quente",
        "score_reason": "Respondeu o produto e o horário; quer comprar já.",
        "suggested_message": "Oi, Mariana! Vi que você procura o Kit verão. Posso te mandar as opções depois das 18h?",
        "whatsapp_url": "https://wa.me/5511987654321?text=Oi%2C%20Mariana%21",
        "campaign": {
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Cadastro kit verão"
        },
        "ad_set": {
            "id": 88,
            "name": "Mulheres 25-45 SP"
        },
        "ad": {
            "id": 140,
            "name": "Anúncio 1"
        },
        "form_id": "987654321098765",
        "received_at": "2026-10-28T14:20:00+00:00",
        "first_contact_at": "2026-10-28T14:35:00+00:00",
        "qualified_at": null,
        "won_at": null,
        "scored_at": "2026-10-28T14:20:09+00:00",
        "events": [
            {
                "id": 902,
                "type": "status_changed",
                "label": "Status alterado",
                "data": {
                    "from": "contacted",
                    "to": "won",
                    "value_cents": 25900
                },
                "user": {
                    "id": 42,
                    "name": "Ana Souza"
                },
                "created_at": "2026-10-28T16:00:00+00:00"
            },
            {
                "id": 901,
                "type": "scored",
                "label": "Qualificado pela IA",
                "data": {
                    "score": "hot"
                },
                "user": null,
                "created_at": "2026-10-28T14:20:09+00:00"
            },
            {
                "id": 900,
                "type": "received",
                "label": "Lead recebido",
                "data": null,
                "user": null,
                "created_at": "2026-10-28T14:20:01+00:00"
            }
        ],
        "links": {
            "panel": "https://app.leadgo.com.br/app/leads/01jbz2k8m4q0t6w2y8b4e0h6na"
        }
    }
}
```

Erros: `validation_failed`, `not_found`.

#### Excluir lead (LGPD)

`DELETE /leads/{id}` · escopo `leads:write`

Apaga de vez o lead e o histórico, para atender ao pedido do titular dos dados. Não apaga na Meta: os leads ficam lá por 90 dias, conforme a política deles.

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/leads/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`.

### Gestor IA

Relatórios da IA que acompanha a conta: pedir análise, aprovar ou recusar as ações propostas e desfazer as aplicadas.

#### Listar relatórios do Gestor IA

`GET /brain/reports` · escopo `brain:read`

Os relatórios da conta, do mais novo para o mais antigo: diários, checagens da tarde, análises pedidas e resumos semanais. pending_actions diz quantas ações esperam decisão. Pagine com cursor (meta.next_cursor).

Parâmetros:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| limit | integer | não | Itens por página, de 1 a 50 (padrão 15). |
| cursor | string | não | meta.next_cursor da página anterior. |

```bash
curl -X GET "https://leadgo.com.br/api/v1/brain/reports" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
            "kind": "daily",
            "kind_label": "Relatório diário",
            "mode": "manual",
            "status": "pending_review",
            "status_label": "Aguardando aprovação",
            "health": "good",
            "health_label": "Saudável",
            "report_date": "2026-10-28",
            "period": {
                "from": "2026-10-21",
                "to": "2026-10-27"
            },
            "diagnosis": "A conta está saudável: o custo por compra caiu 12% na semana. A campanha Promoção de outubro tem espaço para crescer.",
            "yesterday_summary": "Ontem: R$ 312,50 gastos, 24 compras a R$ 13,02 cada.",
            "yesterday": {
                "spend": 312.5,
                "conversions": 24
            },
            "highlights": [
                "CPA 12% abaixo da meta"
            ],
            "error": null,
            "can_decide": true,
            "started_at": "2026-10-28T09:00:02+00:00",
            "generated_at": "2026-10-28T09:01:40+00:00",
            "expires_at": "2026-10-30T09:01:40+00:00",
            "reviewed_at": null,
            "created_at": "2026-10-28T09:00:00+00:00",
            "links": {
                "panel": "https://app.leadgo.com.br/app/brain/reports/01jbz6a0c6f2j8m4q0t6w2y8b4"
            },
            "pending_actions": 1
        }
    ],
    "meta": {
        "per_page": 15,
        "next_cursor": null,
        "prev_cursor": null
    }
}
```

Erros: `validation_failed`.

#### Consultar relatório

`GET /brain/reports/{id}` · escopo `brain:read`

Um relatório com o diagnóstico e as ações. Em actions, kind action se aprova ou recusa (pausar, ativar, mudar verba, realocar); kind suggestion é só para ler (campanha nova, trocar criativo, corrigir rastreamento, esperar). As duas metades de uma realocação têm o mesmo group_key e seguem juntas. Enquanto status for generating, consulte de novo (ou espere o webhook brain.report_ready).

```bash
curl -X GET "https://leadgo.com.br/api/v1/brain/reports/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
        "kind": "daily",
        "kind_label": "Relatório diário",
        "mode": "manual",
        "status": "pending_review",
        "status_label": "Aguardando aprovação",
        "health": "good",
        "health_label": "Saudável",
        "report_date": "2026-10-28",
        "period": {
            "from": "2026-10-21",
            "to": "2026-10-27"
        },
        "diagnosis": "A conta está saudável: o custo por compra caiu 12% na semana. A campanha Promoção de outubro tem espaço para crescer.",
        "yesterday_summary": "Ontem: R$ 312,50 gastos, 24 compras a R$ 13,02 cada.",
        "yesterday": {
            "spend": 312.5,
            "conversions": 24
        },
        "highlights": [
            "CPA 12% abaixo da meta"
        ],
        "error": null,
        "can_decide": true,
        "started_at": "2026-10-28T09:00:02+00:00",
        "generated_at": "2026-10-28T09:01:40+00:00",
        "expires_at": "2026-10-30T09:01:40+00:00",
        "reviewed_at": null,
        "created_at": "2026-10-28T09:00:00+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/brain/reports/01jbz6a0c6f2j8m4q0t6w2y8b4"
        },
        "can_retry": false,
        "actions": [
            {
                "id": "01jbz6c2e8h4k0n6q2t8w4z0b6",
                "report_id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
                "type": "update_campaign_budget",
                "type_label": "Mudar orçamento da campanha",
                "kind": "action",
                "summary": "Mudar orçamento da campanha · Promoção de outubro: R$ 50,00 → R$ 65,00",
                "status": "proposed",
                "status_label": "Aguardando decisão",
                "target": {
                    "type": "campaign",
                    "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                    "name": "Promoção de outubro"
                },
                "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                "params": {
                    "budget_cents": 6500
                },
                "before": {
                    "budget_cents": 5000
                },
                "after": null,
                "reason": "CPA 22% abaixo da meta nos últimos 7 dias, com verba se esgotando antes das 18h.",
                "expected_impact": "+25% de conversões mantendo o CPA.",
                "confidence": "high",
                "priority": 1,
                "is_emergency": false,
                "guard_notes": null,
                "group_key": null,
                "can_decide": true,
                "can_rollback": false,
                "rollback_blocker": "Só ações aplicadas podem ser desfeitas.",
                "decided_at": null,
                "executor": null,
                "executed_at": null,
                "error": null,
                "rolled_back_at": null,
                "evaluation": null
            }
        ]
    }
}
```

Erros: `not_found`.

#### Pedir uma análise agora

`POST /brain/reports` · escopo `brain:manage`

O "Analisar agora" do painel: abre o relatório (status generating) e a IA analisa a conta pela fila em 1 a 3 minutos. Uma por conta por hora (a que falhou não conta). Responde 202 com Location; acompanhe pelo GET ou pelo webhook brain.report_ready.

```bash
curl -X POST "https://leadgo.com.br/api/v1/brain/reports" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 202:

```json
{
    "data": {
        "id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
        "kind": "manual",
        "kind_label": "Análise pedida",
        "mode": "manual",
        "status": "generating",
        "status_label": "Analisando",
        "health": null,
        "health_label": null,
        "report_date": "2026-10-28",
        "period": {
            "from": "2026-10-21",
            "to": "2026-10-27"
        },
        "diagnosis": null,
        "yesterday_summary": null,
        "yesterday": null,
        "highlights": null,
        "error": null,
        "can_decide": false,
        "started_at": "2026-10-28T09:00:02+00:00",
        "generated_at": null,
        "expires_at": null,
        "reviewed_at": null,
        "created_at": "2026-10-28T09:00:00+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/brain/reports/01jbz6a0c6f2j8m4q0t6w2y8b4"
        }
    }
}
```

Erros: `ad_account_not_linked`, `brain_disabled`, `analysis_in_progress`, `analysis_too_soon`.

#### Refazer análise que falhou

`POST /brain/reports/{id}/retry` · escopo `brain:manage`

Refaz a análise de um relatório com status failed (só a mais recente da conta; can_retry diz se dá). Abre um relatório novo e responde 202.

```bash
curl -X POST "https://leadgo.com.br/api/v1/brain/reports/{id}/retry" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 202:

```json
{
    "data": {
        "id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
        "kind": "daily",
        "kind_label": "Relatório diário",
        "mode": "manual",
        "status": "generating",
        "status_label": "Analisando",
        "health": "good",
        "health_label": "Saudável",
        "report_date": "2026-10-28",
        "period": {
            "from": "2026-10-21",
            "to": "2026-10-27"
        },
        "diagnosis": "A conta está saudável: o custo por compra caiu 12% na semana. A campanha Promoção de outubro tem espaço para crescer.",
        "yesterday_summary": "Ontem: R$ 312,50 gastos, 24 compras a R$ 13,02 cada.",
        "yesterday": {
            "spend": 312.5,
            "conversions": 24
        },
        "highlights": [
            "CPA 12% abaixo da meta"
        ],
        "error": null,
        "can_decide": true,
        "started_at": "2026-10-28T09:00:02+00:00",
        "generated_at": "2026-10-28T09:01:40+00:00",
        "expires_at": "2026-10-30T09:01:40+00:00",
        "reviewed_at": null,
        "created_at": "2026-10-28T09:00:00+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/brain/reports/01jbz6a0c6f2j8m4q0t6w2y8b4"
        }
    }
}
```

Erros: `not_found`, `report_not_retryable`, `brain_disabled`, `analysis_in_progress`.

#### Aprovar todas as ações

`POST /brain/reports/{id}/approve` · escopo `brain:manage`

Aprova as ações ainda pendentes do relatório e manda aplicar na Meta pela fila (as já decididas ficam como estão). O relatório espera decisão por 48 horas; depois, expira.

```bash
curl -X POST "https://leadgo.com.br/api/v1/brain/reports/{id}/approve" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
        "kind": "daily",
        "kind_label": "Relatório diário",
        "mode": "manual",
        "status": "approved",
        "status_label": "Aprovado",
        "health": "good",
        "health_label": "Saudável",
        "report_date": "2026-10-28",
        "period": {
            "from": "2026-10-21",
            "to": "2026-10-27"
        },
        "diagnosis": "A conta está saudável: o custo por compra caiu 12% na semana. A campanha Promoção de outubro tem espaço para crescer.",
        "yesterday_summary": "Ontem: R$ 312,50 gastos, 24 compras a R$ 13,02 cada.",
        "yesterday": {
            "spend": 312.5,
            "conversions": 24
        },
        "highlights": [
            "CPA 12% abaixo da meta"
        ],
        "error": null,
        "can_decide": false,
        "started_at": "2026-10-28T09:00:02+00:00",
        "generated_at": "2026-10-28T09:01:40+00:00",
        "expires_at": "2026-10-30T09:01:40+00:00",
        "reviewed_at": null,
        "created_at": "2026-10-28T09:00:00+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/brain/reports/01jbz6a0c6f2j8m4q0t6w2y8b4"
        },
        "can_retry": false,
        "actions": [
            {
                "id": "01jbz6c2e8h4k0n6q2t8w4z0b6",
                "report_id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
                "type": "update_campaign_budget",
                "type_label": "Mudar orçamento da campanha",
                "kind": "action",
                "summary": "Mudar orçamento da campanha · Promoção de outubro: R$ 50,00 → R$ 65,00",
                "status": "queued",
                "status_label": "Na fila",
                "target": {
                    "type": "campaign",
                    "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                    "name": "Promoção de outubro"
                },
                "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                "params": {
                    "budget_cents": 6500
                },
                "before": {
                    "budget_cents": 5000
                },
                "after": null,
                "reason": "CPA 22% abaixo da meta nos últimos 7 dias, com verba se esgotando antes das 18h.",
                "expected_impact": "+25% de conversões mantendo o CPA.",
                "confidence": "high",
                "priority": 1,
                "is_emergency": false,
                "guard_notes": null,
                "group_key": null,
                "can_decide": false,
                "can_rollback": false,
                "rollback_blocker": "Só ações aplicadas podem ser desfeitas.",
                "decided_at": null,
                "executor": null,
                "executed_at": null,
                "error": null,
                "rolled_back_at": null,
                "evaluation": null
            }
        ]
    }
}
```

Erros: `not_found`, `report_not_decidable`.

#### Recusar todas as ações

`POST /brain/reports/{id}/reject` · escopo `brain:manage`

Recusa as ações ainda pendentes do relatório. Nada muda na Meta.

```bash
curl -X POST "https://leadgo.com.br/api/v1/brain/reports/{id}/reject" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
        "kind": "daily",
        "kind_label": "Relatório diário",
        "mode": "manual",
        "status": "rejected",
        "status_label": "Recusado",
        "health": "good",
        "health_label": "Saudável",
        "report_date": "2026-10-28",
        "period": {
            "from": "2026-10-21",
            "to": "2026-10-27"
        },
        "diagnosis": "A conta está saudável: o custo por compra caiu 12% na semana. A campanha Promoção de outubro tem espaço para crescer.",
        "yesterday_summary": "Ontem: R$ 312,50 gastos, 24 compras a R$ 13,02 cada.",
        "yesterday": {
            "spend": 312.5,
            "conversions": 24
        },
        "highlights": [
            "CPA 12% abaixo da meta"
        ],
        "error": null,
        "can_decide": false,
        "started_at": "2026-10-28T09:00:02+00:00",
        "generated_at": "2026-10-28T09:01:40+00:00",
        "expires_at": "2026-10-30T09:01:40+00:00",
        "reviewed_at": null,
        "created_at": "2026-10-28T09:00:00+00:00",
        "links": {
            "panel": "https://app.leadgo.com.br/app/brain/reports/01jbz6a0c6f2j8m4q0t6w2y8b4"
        },
        "can_retry": false,
        "actions": [
            {
                "id": "01jbz6c2e8h4k0n6q2t8w4z0b6",
                "report_id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
                "type": "update_campaign_budget",
                "type_label": "Mudar orçamento da campanha",
                "kind": "action",
                "summary": "Mudar orçamento da campanha · Promoção de outubro: R$ 50,00 → R$ 65,00",
                "status": "rejected",
                "status_label": "Recusada",
                "target": {
                    "type": "campaign",
                    "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                    "name": "Promoção de outubro"
                },
                "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
                "params": {
                    "budget_cents": 6500
                },
                "before": {
                    "budget_cents": 5000
                },
                "after": null,
                "reason": "CPA 22% abaixo da meta nos últimos 7 dias, com verba se esgotando antes das 18h.",
                "expected_impact": "+25% de conversões mantendo o CPA.",
                "confidence": "high",
                "priority": 1,
                "is_emergency": false,
                "guard_notes": null,
                "group_key": null,
                "can_decide": false,
                "can_rollback": false,
                "rollback_blocker": "Só ações aplicadas podem ser desfeitas.",
                "decided_at": null,
                "executor": null,
                "executed_at": null,
                "error": null,
                "rolled_back_at": null,
                "evaluation": null
            }
        ]
    }
}
```

Erros: `not_found`, `report_not_decidable`.

#### Aprovar uma ação

`POST /brain/actions/{id}/approve` · escopo `brain:manage`

Aprova a ação e manda aplicar na Meta pela fila. Numa realocação, a aplicação só sai quando as duas metades (mesmo group_key) estão aprovadas.

```bash
curl -X POST "https://leadgo.com.br/api/v1/brain/actions/{id}/approve" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz6c2e8h4k0n6q2t8w4z0b6",
        "report_id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
        "type": "update_campaign_budget",
        "type_label": "Mudar orçamento da campanha",
        "kind": "action",
        "summary": "Mudar orçamento da campanha · Promoção de outubro: R$ 50,00 → R$ 65,00",
        "status": "queued",
        "status_label": "Na fila",
        "target": {
            "type": "campaign",
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Promoção de outubro"
        },
        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "params": {
            "budget_cents": 6500
        },
        "before": {
            "budget_cents": 5000
        },
        "after": null,
        "reason": "CPA 22% abaixo da meta nos últimos 7 dias, com verba se esgotando antes das 18h.",
        "expected_impact": "+25% de conversões mantendo o CPA.",
        "confidence": "high",
        "priority": 1,
        "is_emergency": false,
        "guard_notes": null,
        "group_key": null,
        "can_decide": false,
        "can_rollback": false,
        "rollback_blocker": "Só ações aplicadas podem ser desfeitas.",
        "decided_at": "2026-10-28T10:15:00+00:00",
        "executor": "user",
        "executed_at": null,
        "error": null,
        "rolled_back_at": null,
        "evaluation": null
    }
}
```

Erros: `not_found`, `action_not_decidable`, `report_not_decidable`.

#### Recusar uma ação

`POST /brain/actions/{id}/reject` · escopo `brain:manage`

Recusa a ação; numa realocação, a outra metade é recusada junto.

```bash
curl -X POST "https://leadgo.com.br/api/v1/brain/actions/{id}/reject" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz6c2e8h4k0n6q2t8w4z0b6",
        "report_id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
        "type": "update_campaign_budget",
        "type_label": "Mudar orçamento da campanha",
        "kind": "action",
        "summary": "Mudar orçamento da campanha · Promoção de outubro: R$ 50,00 → R$ 65,00",
        "status": "rejected",
        "status_label": "Recusada",
        "target": {
            "type": "campaign",
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Promoção de outubro"
        },
        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "params": {
            "budget_cents": 6500
        },
        "before": {
            "budget_cents": 5000
        },
        "after": null,
        "reason": "CPA 22% abaixo da meta nos últimos 7 dias, com verba se esgotando antes das 18h.",
        "expected_impact": "+25% de conversões mantendo o CPA.",
        "confidence": "high",
        "priority": 1,
        "is_emergency": false,
        "guard_notes": null,
        "group_key": null,
        "can_decide": false,
        "can_rollback": false,
        "rollback_blocker": "Só ações aplicadas podem ser desfeitas.",
        "decided_at": "2026-10-28T10:15:00+00:00",
        "executor": null,
        "executed_at": null,
        "error": null,
        "rolled_back_at": null,
        "evaluation": null
    }
}
```

Erros: `not_found`, `action_not_decidable`, `report_not_decidable`.

#### Desfazer uma ação aplicada

`POST /brain/actions/{id}/rollback` · escopo `brain:manage`

Volta ao estado anterior (verba ou status) uma ação aplicada há menos de 24 horas, se ninguém mexeu no alvo depois. can_rollback e rollback_blocker dizem se dá e por quê.

```bash
curl -X POST "https://leadgo.com.br/api/v1/brain/actions/{id}/rollback" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz6c2e8h4k0n6q2t8w4z0b6",
        "report_id": "01jbz6a0c6f2j8m4q0t6w2y8b4",
        "type": "update_campaign_budget",
        "type_label": "Mudar orçamento da campanha",
        "kind": "action",
        "summary": "Mudar orçamento da campanha · Promoção de outubro: R$ 50,00 → R$ 65,00",
        "status": "executed",
        "status_label": "Aplicada",
        "target": {
            "type": "campaign",
            "id": "01jby0q4c8m2d6t9v3k7w5r1xa",
            "name": "Promoção de outubro"
        },
        "campaign_id": "01jby0q4c8m2d6t9v3k7w5r1xa",
        "params": {
            "budget_cents": 6500
        },
        "before": {
            "budget_cents": 5000
        },
        "after": null,
        "reason": "CPA 22% abaixo da meta nos últimos 7 dias, com verba se esgotando antes das 18h.",
        "expected_impact": "+25% de conversões mantendo o CPA.",
        "confidence": "high",
        "priority": 1,
        "is_emergency": false,
        "guard_notes": null,
        "group_key": null,
        "can_decide": false,
        "can_rollback": false,
        "rollback_blocker": "Esta ação já foi desfeita.",
        "decided_at": null,
        "executor": null,
        "executed_at": null,
        "error": null,
        "rolled_back_at": "2026-10-28T12:00:00+00:00",
        "evaluation": null
    }
}
```

Erros: `not_found`, `rollback_unavailable`, `operation_failed`.

### Rascunho pela IA

A IA do LeadGo monta a campanha a partir de um briefing; você revisa e cria.

#### Montar campanha com a IA

`POST /campaign-briefs` · escopo `ai:draft`

O mesmo "Criar com a IA" do painel: a partir do objetivo, da verba, da região, do link, da descrição livre e das mídias, a IA escreve nome, público, interesses e a copy de cada anúncio. Nada é publicado: a resposta traz data.campaign, pronto para revisar e enviar em POST /campaigns. Leva até ~2 minutos (configure o timeout do seu cliente para 180 s). Limite de 15 por hora por pessoa, somado ao painel.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| objective | string | sim | sales, leads, lead_form, traffic, awareness, whatsapp. |
| description | string | sim | O que é anunciado, para quem e o diferencial (10 a 2000 caracteres). |
| budget.amount_cents | integer | sim | Verba diária em centavos. |
| link_url | string | não | Obrigatório nos objetivos com link (sales, leads, traffic, awareness). |
| media_ids | string[] | sim | Mídias prontas (1 a 10); a IA escreve uma copy para cada. |
| persona_id | integer | não | Com persona, o público é o dela e a IA escreve para ela. |
| targeting.countries | string[] | não | Países (padrão BR). |
| targeting.cities | object[] | não | Cidades (de GET /targeting/cities). |
| tracking.pixel_id | string | não | Pixel, nos objetivos sales e leads (sem ele, o padrão da conta). |
| whatsapp.phone_number | string | não | Número do WhatsApp (E.164), no objetivo whatsapp. |
| lead_form.privacy_policy_url | string | não | Política de privacidade, no objetivo lead_form (sem ela, a de Leads › Configurações). |

```bash
curl -X POST "https://leadgo.com.br/api/v1/campaign-briefs" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "objective": "sales",
    "description": "Loja de suplementos para mulheres que treinam. Whey e creatina com 30% off e frete grátis acima de R$ 199.",
    "budget": {
        "amount_cents": 5000
    },
    "link_url": "https://loja.exemplo.com.br/suplementos",
    "media_ids": [
        "01jbx3m7k2q8f5v9w4n6c0r1ht"
    ]
}'
```

Resposta 200:

```json
{
    "data": {
        "campaign": {
            "name": "Vendas | Suplementos | Outubro",
            "goal": "conversions",
            "status": "paused",
            "budget": {
                "type": "daily",
                "amount_cents": 5000
            },
            "tracking": {
                "conversion_event": "PURCHASE"
            },
            "targeting": {
                "countries": [
                    "BR"
                ],
                "age_min": 25,
                "age_max": 45,
                "gender": "female",
                "interests": [
                    {
                        "id": "6003107902433",
                        "name": "Fitness"
                    }
                ]
            },
            "link_url": "https://loja.exemplo.com.br/suplementos",
            "creatives": [
                {
                    "media_id": "01jbx3m7k2q8f5v9w4n6c0r1ht",
                    "headline": "Whey com 30% off",
                    "body": "Treine melhor com o whey que cabe no bolso.",
                    "description": "Frete grátis",
                    "call_to_action": "SHOP_NOW"
                }
            ]
        },
        "rationale": "Público feminino fitness com interesse em suplementos.",
        "unresolved_interests": []
    }
}
```

Erros: `validation_failed`, `ai_rate_limited`, `ai_unavailable`, `ad_account_not_linked`.

### Webhooks

URLs do seu sistema que recebem os avisos das campanhas e das mídias.

#### Listar webhooks

`GET /webhooks` · escopo `webhooks:manage`

Webhooks da conta (até 5). O segredo não aparece aqui.

```bash
curl -X GET "https://leadgo.com.br/api/v1/webhooks" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": [
        {
            "id": "01jbz8e2h6k0n4r8u2x6b0f4jm",
            "url": "https://erp.loja.exemplo.com.br/hooks/leadgo",
            "description": "ERP",
            "events": [
                "campaign.published",
                "campaign.failed",
                "campaign.status_changed"
            ],
            "is_active": true,
            "failure_count": 0,
            "last_error": null,
            "last_delivered_at": "2026-10-28T15:10:00+00:00",
            "created_at": "2026-10-28T15:00:00+00:00"
        }
    ]
}
```

#### Criar webhook

`POST /webhooks` · escopo `webhooks:manage`

A URL https pública que recebe os avisos. O segredo para conferir a assinatura vem só nesta resposta (secret). Sem events, assina todos.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| url | string | sim | URL https pública (sem redirecionamento). |
| description | string | não | Para reconhecer na lista. |
| events | string[] | não | campaign.published, campaign.failed, campaign.status_changed, campaign.deleted, ad.approved, ad.rejected, alert.created, ad_account.status_changed, lead.created, lead.status_changed, lead.scored, brain.report_ready, media.ready, media.failed. Sem o campo, todos os que quem criou o token pode assinar: os lead.* pedem a permissão de ver leads e o brain.report_ready a de ver o Gestor IA. |

```bash
curl -X POST "https://leadgo.com.br/api/v1/webhooks" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://erp.loja.exemplo.com.br/hooks/leadgo",
    "description": "ERP",
    "events": [
        "campaign.published",
        "campaign.failed",
        "campaign.status_changed"
    ]
}'
```

Resposta 201:

```json
{
    "data": {
        "id": "01jbz8e2h6k0n4r8u2x6b0f4jm",
        "url": "https://erp.loja.exemplo.com.br/hooks/leadgo",
        "description": "ERP",
        "events": [
            "campaign.published",
            "campaign.failed",
            "campaign.status_changed"
        ],
        "is_active": true,
        "failure_count": 0,
        "last_error": null,
        "last_delivered_at": null,
        "created_at": "2026-10-28T15:00:00+00:00",
        "secret": "whsec_8hJ2kL9mN3pQ5rS7tV1wX4yZ6aB0cD2eF4gH6jK8"
    }
}
```

Erros: `validation_failed`, `webhook_limit_reached`.

#### Consultar webhook

`GET /webhooks/{id}` · escopo `webhooks:manage`

Um webhook, com a última entrega e o último erro.

```bash
curl -X GET "https://leadgo.com.br/api/v1/webhooks/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz8e2h6k0n4r8u2x6b0f4jm",
        "url": "https://erp.loja.exemplo.com.br/hooks/leadgo",
        "description": "ERP",
        "events": [
            "campaign.published",
            "campaign.failed",
            "campaign.status_changed"
        ],
        "is_active": true,
        "failure_count": 0,
        "last_error": null,
        "last_delivered_at": "2026-10-28T15:10:00+00:00",
        "created_at": "2026-10-28T15:00:00+00:00"
    }
}
```

Erros: `not_found`.

#### Alterar webhook

`PATCH /webhooks/{id}` · escopo `webhooks:manage`

URL, eventos, ligar/desligar (is_active) e troca do segredo (rotate_secret: o novo vem só nesta resposta). Religar zera a contagem de falhas.

Corpo:

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| url | string | não | Nova URL. |
| description | string | não | Nova descrição. |
| events | string[] | não | Eventos assinados. |
| is_active | boolean | não | Liga ou desliga os avisos. |
| rotate_secret | boolean | não | Gera um segredo novo. |

```bash
curl -X PATCH "https://leadgo.com.br/api/v1/webhooks/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": true,
    "rotate_secret": true
}'
```

Resposta 200:

```json
{
    "data": {
        "id": "01jbz8e2h6k0n4r8u2x6b0f4jm",
        "url": "https://erp.loja.exemplo.com.br/hooks/leadgo",
        "description": "ERP",
        "events": [
            "campaign.published",
            "campaign.failed",
            "campaign.status_changed"
        ],
        "is_active": true,
        "failure_count": 0,
        "last_error": null,
        "last_delivered_at": "2026-10-28T15:10:00+00:00",
        "created_at": "2026-10-28T15:00:00+00:00",
        "secret": "whsec_3nM5pQ7rS9tV1wX3yZ5aB7cD9eF1gH3jK5mN7pQ9"
    }
}
```

Erros: `validation_failed`, `not_found`.

#### Excluir webhook

`DELETE /webhooks/{id}` · escopo `webhooks:manage`

Os avisos para esta URL param na hora.

```bash
curl -X DELETE "https://leadgo.com.br/api/v1/webhooks/{id}" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 204, sem corpo.

Erros: `not_found`.

#### Testar webhook

`POST /webhooks/{id}/test` · escopo `webhooks:manage`

Envia agora um aviso webhook.test, assinado, e diz o que a URL respondeu. Não conta para desligar o webhook.

```bash
curl -X POST "https://leadgo.com.br/api/v1/webhooks/{id}/test" \
  -H "Authorization: Bearer $LEADGO_TOKEN" \
  -H "Accept: application/json"
```

Resposta 200:

```json
{
    "data": {
        "delivered": true,
        "status": 200,
        "error": null,
        "event_id": "evt_01jbz9a1c5f9j3m7q1t5x9b3e7"
    }
}
```

Erros: `not_found`.

