{
  "openapi": "3.1.0",
  "info": {
    "title": "owiew: API de leitura",
    "version": "1.0.0",
    "summary": "Investimento em mídia paga, auditável, por cliente.",
    "description": "API de LEITURA do owiew. Ela devolve o que as plataformas de anúncio\nreportaram, exatamente como reportaram, com a trilha de quando cada número\nfoi observado.\n\n## Antes de escrever uma linha de código, quatro coisas\n\n**1. Dinheiro está em micros.** Todo campo terminado em `_micros` é um\ninteiro em que 1.000.000 vale uma unidade da moeda. Divida por 1.000.000,\ncom aritmética inteira. Tratar micros como centavos infla tudo dez mil vezes,\ne o erro é grande demais para passar despercebido — o perigoso é o inverso,\nquem divide por 1.000.000 em ponto flutuante e perde um centavo por linha.\n\n**2. Conversões TAMBÉM estão em micros.** `conversions_micros: 75000000` são\n75 conversões, não 75 milhões. A plataforma reporta conversão fracionária\npor causa de atribuição parcial, e é por isso que o campo não é inteiro.\n\n**3. Métrica derivada não vem pronta, e é de propósito.** CPA, CPC, CTR e\nROAS são calculados a partir dos totais somados — nunca pela média das\nlinhas. Enviá-los prontos convidaria a somá-los, e somar razões é errado.\n\n**4. O mesmo número pode ser reescrito depois.** As plataformas revisam\ndado já reportado por dias. Se você guarda histórico, reconcilie por\n`/audit/revisions` — sem isso o seu banco diverge do nosso em silêncio.\n\n## Autenticação, em dois passos\n\n**1. Troque a chave por um token.** A chave começa com `adv_` e vai no\ncabeçalho — nunca no corpo, nunca na URL:\n\n```\ncurl -s -X POST https://dash.owiew.com/v1/token \\\n  -H \"Authorization: Bearer $CHAVE_DE_API\"\n```\n\n**2. Use o token nas leituras.** Ele é assinado, dura **dois minutos** e é\nescopado a um cliente e a um conjunto de contas. Renove quando expirar —\nnão guarde, e não tente adivinhar quando falta pouco: a resposta traz\n`expires_at`.\n\nA vida curta é intencional. É o que permite revogar um acesso sem manter\nlista de revogação, e o que faz cada leitura ser verificada por assinatura,\nsem consultar banco nenhum.\n\n### O que a chave nunca pode fazer\n\n**Escrever.** O papel é restrito a `viewer` e `analyst` — os dois únicos que\nnão concedem execução na plataforma de anúncio nem reescrita da\nclassificação do relatório. A restrição é imposta pelo servidor,\nna criação da chave e de novo na emissão do token.\n\nNão é excesso de zelo: a permissão de execução gasta dinheiro real, e as três\nbarreiras que a protegem (interruptor por cliente, confirmação na tela,\ntrilha imutável) foram desenhadas para uma pessoa que decide. Uma chave no\narquivo de configuração de um servidor de terceiro não passa por nenhuma.\n\n### Prazo e rotação\n\nToda chave expira — não existe opção \"nunca\". O padrão é 90 dias e o teto é\n365. Quando ela vence, a troca passa a responder `401` e a integração para:\né uma falha visível e reversível, em vez de uma porta que ninguém lembra de\nfechar.\n\n## Isolamento\n\nO cliente vem sempre do CAMINHO (`/v1/t/{tenantID}/...`) e é conferido\ncontra o token. Não há cabeçalho nem cookie que troque de cliente. Um token\nusado num caminho de outro cliente recebe 403 — nunca uma resposta vazia,\nque seria indistinguível de 'não há dados'.",
    "contact": {
      "name": "owiew",
      "url": "https://owiew.com"
    }
  },
  "servers": [
    {
      "url": "https://dash.owiew.com",
      "description": "Produção"
    }
  ],
  "components": {
    "securitySchemes": {
      "chaveDeAPI": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "adv_...",
        "description": "A credencial de MÁQUINA, criada por quem administra o cliente. Vale só em `POST /v1/token` — ela não abre nenhuma rota de leitura diretamente, e é isso que permite à API verificar cada requisição por assinatura, sem consultar banco."
      },
      "tokenDeAcesso": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Ed25519 compacto",
        "description": "Token de acesso assinado, com dois minutos de validade, escopado a um cliente e a um conjunto de contas. Obtido em `POST /v1/token`."
      }
    },
    "schemas": {
      "Totais": {
        "type": "object",
        "description": "Métricas ADITIVAS: somar duas linhas destas produz o total correto. Note que NÃO há CPA, CPC, CTR nem ROAS aqui, e a ausência é deliberada — ver a descrição de `conversions_micros`.",
        "properties": {
          "spend_micros": {
            "type": "integer",
            "format": "int64",
            "description": "Investimento em MICROS: divida por 1.000.000 para obter o valor na moeda. 1.500.000 é 1,50. Nunca use ponto flutuante para converter — multiplique e divida como inteiro."
          },
          "impressions": {
            "type": "integer",
            "format": "int64",
            "description": "Impressões."
          },
          "clicks": {
            "type": "integer",
            "format": "int64",
            "description": "Cliques."
          },
          "conversions_micros": {
            "type": "integer",
            "format": "int64",
            "description": "Conversões, TAMBÉM EM MICROS. A plataforma reporta conversões fracionárias (atribuição parcial), então a contagem não é inteira. Divida por 1.000.000 para ter o número de conversões. Exibir este campo cru já produziu um painel anunciando '75 milhões de conversões' onde havia 75."
          },
          "conversion_value_micros": {
            "type": "integer",
            "format": "int64",
            "description": "Valor das conversões reportado pela plataforma em MICROS: divida por 1.000.000 para obter o valor na moeda. 1.500.000 é 1,50. Nunca use ponto flutuante para converter — multiplique e divida como inteiro."
          },
          "currency": {
            "type": "string",
            "description": "Moeda da CONTA, código ISO-4217. Contas de moedas diferentes NÃO podem ser somadas sem conversão — o total viria numericamente certo e semanticamente sem sentido."
          },
          "revisions": {
            "type": "integer",
            "description": "Quantas alterações retroativas afetam este recorte. Maior que zero significa que a plataforma mudou números que já havia reportado."
          }
        }
      },
      "TaxonomiaDePublico": {
        "type": "object",
        "description": "As listas FECHADAS do construtor, os limites de tamanho e os pisos de cada plataforma. É a mesma resposta para todo cliente.\n\n**Leia esta rota antes de gravar um briefing.** Os cinco eixos são validados pelo servidor na gravação: um valor que não está aqui é recusado na gravação, e não há como descobri-lo por tentativa sem tomar 400.",
        "properties": {
          "mercados": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "b2c",
                "b2b",
                "ambos"
              ]
            }
          },
          "naturezas": {
            "type": "array",
            "description": "A natureza da compra.",
            "items": {
              "type": "string",
              "enum": [
                "essencial",
                "opcional",
                "aspiracional",
                "recorrente",
                "sazonal",
                "emergencial"
              ]
            }
          },
          "tangibilidades": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "fisico",
                "alimentar",
                "utilidade_pratica",
                "emocional",
                "servico_profissional",
                "infoproduto",
                "financeiro",
                "saude"
              ]
            }
          },
          "tickets": {
            "type": "array",
            "description": "As faixas de ticket, de `t1` a `t6`.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "`t1` a `t6`."
                },
                "de": {
                  "type": "integer",
                  "description": "Piso da faixa, **em REAIS** — não em micros e não em centavos. É o único lugar da API onde dinheiro não está em micros, porque aqui ele é rótulo de faixa e não valor medido."
                },
                "ate": {
                  "type": "integer",
                  "description": "Teto da faixa, em reais. **`0` significa SEM TETO**, e só acontece na `t6`. Tratar o zero como limite superior faria a faixa mais cara do catálogo virar a mais barata."
                }
              }
            }
          },
          "abrangencias": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "nacional",
                "estados",
                "cidades",
                "raio",
                "internacional"
              ]
            }
          },
          "bases_legais": {
            "type": "array",
            "description": "As bases legais aceitas na importação de lista. São CÓDIGOS: cada um exige uma coisa diferente de quem importa, e a explicação do que cada um exige NÃO vem nesta lista — ela é da interface.",
            "items": {
              "type": "string",
              "enum": [
                "consentimento",
                "contrato",
                "legitimo_interesse"
              ]
            }
          },
          "campos_de_lista": {
            "type": "array",
            "description": "Os campos que uma coluna do CSV pode virar no mapeamento.\n\n**`nome_completo` é campo de ENTRADA apenas.** Ele existe porque a maior parte dos CRMs exporta o nome inteiro numa coluna só, e as plataformas casam primeiro nome e sobrenome SEPARADOS — mapear essa coluna como `nome` produz sobrenome vazio em 100% das linhas, sem erro nenhum. Ele nunca sai como coluna de exportação.",
            "items": {
              "type": "string",
              "enum": [
                "email",
                "telefone",
                "nome",
                "sobrenome",
                "nome_completo",
                "cidade",
                "uf",
                "cep",
                "pais",
                "ignorar"
              ]
            }
          },
          "origens": {
            "type": "array",
            "description": "As origens de público que existem. **Leia daqui** em vez de digitar a lista: é a mesma regra dos outros vocabulários desta rota, e uma origem nova aparece sozinha para quem integra.",
            "items": {
              "type": "string",
              "enum": [
                "montado",
                "semelhante"
              ]
            }
          },
          "limites": {
            "type": "object",
            "description": "Tetos de tamanho, em caracteres (contados em runas, não em bytes).",
            "properties": {
              "nome": {
                "type": "integer"
              },
              "descricao": {
                "type": "integer"
              },
              "nicho": {
                "type": "integer"
              },
              "diferenciais": {
                "type": "integer"
              },
              "raio_km_maximo": {
                "type": "integer",
                "description": "Em quilômetros."
              },
              "retencao_dias_maxima": {
                "type": "integer",
                "description": "Teto de retenção de uma lista, em dias. Acima disso a lista casa mal — as pessoas trocaram de e-mail — e continua sendo dado pessoal de gente que talvez nem seja mais cliente."
              }
            }
          },
          "minimos_de_plataforma": {
            "type": "object",
            "description": "Quantas linhas APROVEITADAS cada plataforma exige para semear um público semelhante. **Não é alcance**: quantas ela casa com gente real é ela quem diz, depois do envio, e costuma ser bem menos.",
            "properties": {
              "meta": {
                "type": "integer"
              },
              "meta_recomendado": {
                "type": "integer"
              },
              "google": {
                "type": "integer"
              },
              "tiktok": {
                "type": "integer"
              }
            }
          }
        }
      },
      "PerfilDeRede": {
        "type": "object",
        "description": "O perfil MÉDIO de uma rede: para onde a base dela pende quando nada é dito. **Não é regra sobre um anúncio** — é o ponto de partida de quem ainda não segmentou nada.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Ex.: `meta_instagram`."
          },
          "plataforma": {
            "type": "string",
            "enum": [
              "meta",
              "google",
              "tiktok",
              "linkedin",
              "x",
              "pinterest",
              "kwai"
            ]
          },
          "idade_nucleo": {
            "type": "array",
            "maxItems": 2,
            "minItems": 2,
            "items": {
              "type": "integer"
            },
            "description": "`[inicio, fim]` POSICIONAL, não objeto: onde está a maior parte da base."
          },
          "idade_cauda": {
            "type": "array",
            "maxItems": 2,
            "minItems": 2,
            "items": {
              "type": "integer"
            },
            "description": "`[inicio, fim]`: até onde a rede ainda entrega volume, com custo maior."
          },
          "classes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Classes predominantes, em ordem de peso. Não se traduzem."
          },
          "custo_cpm_relativo": {
            "type": "number",
            "description": "ÍNDICE RELATIVO, **nunca reais**: `1,00` é a mediana destas dez redes. Ler `0,60` como previsão de preço é como um orçamento nasce errado — a rede mais barata por mil impressões pode ser a mais cara por venda."
          },
          "custo_cpc_relativo": {
            "type": "number",
            "description": "Índice relativo. Ver `custo_cpm_relativo`."
          },
          "custo_cpl_relativo": {
            "type": "number",
            "description": "Índice relativo. Ver `custo_cpm_relativo`."
          },
          "intencao": {
            "type": "string",
            "enum": [
              "descoberta",
              "consideracao",
              "declarada",
              "planejamento",
              "mista"
            ],
            "description": "Em que estado de intenção a pessoa está quando encontra o anúncio. `declarada` é quem foi procurar; `descoberta` é quem não estava procurando nada."
          },
          "teto_de_ticket": {
            "type": "string",
            "description": "Acima desta faixa a rede entrega volume sem venda. Vazio significaria sem teto conhecido."
          },
          "piso_de_ticket": {
            "type": "string",
            "description": "Abaixo desta faixa o custo da rede não fecha a conta."
          },
          "serve_b2b": {
            "type": "boolean"
          },
          "exige_video_nativo": {
            "type": "boolean",
            "description": "Quando verdadeiro, peça estática não roda ali. É custo de produção e entra na decisão antes do orçamento."
          },
          "sofre_em_regiao_pequena": {
            "type": "boolean"
          },
          "tangibilidades": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "As tangibilidades em que a rede vai bem. **Vazio ou `null` significa TODAS**, e não nenhuma."
          }
        }
      },
      "CabecalhoDoPublico": {
        "type": "object",
        "description": "Identidade e ORIGEM de um público. É a única parte da resposta que as duas origens sempre trazem.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "origem": {
            "type": "string",
            "enum": [
              "montado",
              "semelhante"
            ],
            "description": "De onde ele veio. `montado` saiu do questionário de briefing; `semelhante` saiu de um CSV de clientes importado.\n\n**Ela não decide sozinha o que a resposta traz.** Quem integra deve desenhar pela PRESENÇA de cada bloco — uma origem nova aqui não deve quebrar um cliente que só sabia das duas."
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicoNaLista": {
        "type": "object",
        "description": "Um público na listagem. A rota devolve as DUAS origens misturadas, em ordem única — separá-las em duas listas reconstruiria as duas áreas que a unificação desfez.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "origem": {
            "type": "string",
            "enum": [
              "montado",
              "semelhante"
            ]
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "resumo": {
            "type": "object",
            "description": "O que aquela origem tem de próprio para caber numa linha. **Todo campo é opcional**, e a ausência é informação.",
            "properties": {
              "ticket": {
                "type": "string",
                "description": "A faixa (`t1`–`t6`), só em `montado`."
              },
              "mercado": {
                "type": "string",
                "description": "Só em `montado`."
              },
              "aproveitadas": {
                "type": "integer",
                "description": "Linhas que viraram contato único, só em `semelhante`. **NÃO É ALCANCE** — quantas a plataforma casa com gente real é ela quem diz, depois do envio, e costuma ser bem menos."
              },
              "avisos": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Os códigos de qualidade da base, só em `semelhante`. Lista vazia significa «nenhum aviso»; campo ausente significa «esta origem não tem avisos»."
              }
            }
          }
        }
      },
      "Briefing": {
        "type": "object",
        "description": "O que o cliente DECLAROU sobre o próprio negócio. É declaração de gente: nenhuma API descobre que a pousada atende num raio de 40 km, e nada aqui pode ser reconstruído depois a partir das plataformas.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "mercado": {
            "type": "string"
          },
          "natureza": {
            "type": "string"
          },
          "tangibilidade": {
            "type": "string"
          },
          "ticket": {
            "type": "string",
            "description": "A faixa (`t1`–`t6`), não o valor."
          },
          "regiao": {
            "type": "object",
            "description": "Só os campos da abrangência escolhida vêm preenchidos; os outros vêm vazios ou `null`.",
            "properties": {
              "abrangencia": {
                "type": "string"
              },
              "ufs": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Siglas, quando `estados`."
              },
              "municipios": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Quando `cidades`."
              },
              "centro": {
                "type": "string",
                "description": "Quando `raio`."
              },
              "raio_km": {
                "type": "integer",
                "description": "Em quilômetros, quando `raio`."
              },
              "paises": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "ISO de 2 letras, quando `internacional`."
              },
              "eh_local": {
                "type": "boolean",
                "description": "CALCULADO, não declarado: `raio`, ou `cidades` com no máximo três municípios. É o que faz rede de alcance largo cair no ranking."
              }
            }
          },
          "descricao": {
            "type": "string"
          },
          "nicho": {
            "type": "string"
          },
          "diferenciais": {
            "type": "string",
            "description": "Texto livre. **Nenhum modelo de linguagem o lê**: ele entra só como palavra-chave para ordenar os termos sugeridos."
          },
          "ajuste": {
            "type": "object",
            "description": "A DISCORDÂNCIA do cliente com a proposta — a única parte dela que é persistida. Discordância é declaração; recomendação é cálculo.",
            "properties": {
              "idade_minima": {
                "type": "integer",
                "description": "`0` = não ajustado."
              },
              "idade_maxima": {
                "type": "integer",
                "description": "`0` = não ajustado."
              },
              "genero": {
                "type": "string",
                "description": "Vazio = não ajustado."
              },
              "classes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "observacao": {
                "type": "string"
              },
              "vazio": {
                "type": "boolean",
                "description": "CALCULADO: nenhum dos cinco acima foi preenchido."
              }
            }
          },
          "criado_por": {
            "type": "string",
            "description": "O sujeito do token verificado de quem criou. Nunca vem do corpo da requisição."
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicoCompleto": {
        "type": "object",
        "description": "Um público, seja qual for a origem dele.\n\n**Só `publico` vem sempre.** Os outros oito blocos dependem da origem, e a ausência de um deles é a resposta certa e não uma falha: desenhe pela presença, nunca pelo valor de `origem`.\n\n**Nada além de `publico`, `briefing` e `lista` existe no banco.** `sugestao`, `redes` e `termos` são recalculados a cada leitura: a mesma requisição pode devolver outro ranking amanhã, sem ninguém ter editado nada. Guardar esta resposta e comparar com a de ontem como se fosse mudança do cliente produz alerta falso.",
        "properties": {
          "publico": {
            "$ref": "#/components/schemas/CabecalhoDoPublico"
          },
          "briefing": {
            "$ref": "#/components/schemas/Briefing"
          },
          "sugestao": {
            "type": "object",
            "description": "O recorte proposto, já com o ajuste do cliente aplicado. **Só em `origem: montado`.**",
            "properties": {
              "idade_minima": {
                "type": "integer"
              },
              "idade_maxima": {
                "type": "integer"
              },
              "genero": {
                "type": "string",
                "description": "`equilibrado` na proposta original. **O gênero NÃO é presumido a partir da categoria do produto** — recortar por ele é escolha do cliente, e o motivo `genero_nao_presumido` diz isso na tela."
              },
              "classes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "papel_de_decisao": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Quem decide a compra. Só vem em `b2b` e `ambos`; em B2C é `null`. **Sem vocabulário fechado declarado** — ver a dívida da entrega de 2026-09-28."
              },
              "ciclo_minimo_dias": {
                "type": "integer",
                "description": "Em DIAS."
              },
              "ciclo_maximo_dias": {
                "type": "integer",
                "description": "Em DIAS. É o que define a janela de atribuição honesta: medir uma venda de ciclo de 45 dias numa janela de 7 faz o canal parecer ruim quando ele não é."
              },
              "objetivo": {
                "type": "string",
                "enum": [
                  "alcance",
                  "video",
                  "trafego",
                  "lead",
                  "conversao"
                ]
              },
              "ajustado": {
                "type": "boolean",
                "description": "O cliente corrigiu algo da proposta original."
              },
              "motivos": {
                "type": "array",
                "description": "Por que este recorte, eixo por eixo.",
                "items": {
                  "type": "object",
                  "properties": {
                    "codigo": {
                      "type": "string",
                      "description": "Código fechado, traduzido na tela. É CÓDIGO e não frase: a frase muda, e uma frase gravada em 2026 continuaria aparecendo depois de o texto ter sido reescrito. Código desconhecido deve ser exibido CRU, nunca omitido — a linha que some leva junto a informação de que ela existia."
                    },
                    "eixo": {
                      "type": "string",
                      "description": "O eixo do briefing que originou o motivo (`ticket`, `mercado`, `tangibilidade`, `natureza`). **Pode vir vazio**: há motivos sem eixo, como `genero_nao_presumido` e `custo_acima_da_mediana`."
                    }
                  }
                }
              }
            }
          },
          "redes": {
            "type": "array",
            "description": "As dez redes, ordenadas por nota decrescente e desempatadas por `id`. **Vêm sempre todas**, inclusive as que não servem — saber que uma rede foi considerada e reprovada é informação; ela sumir da lista não é. **Só em `origem: montado`.**",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "plataforma": {
                  "type": "string"
                },
                "nota": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Serve para ORDENAR, e só. Não é previsão de resultado, nem probabilidade, nem score de confiança. A diferença entre 78 e 74 não significa nada fora da ordem."
                },
                "favoravel": {
                  "type": "array",
                  "description": "O que pesou a favor. Pode vir vazio.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "codigo": {
                        "type": "string",
                        "description": "Código fechado, traduzido na tela. É CÓDIGO e não frase: a frase muda, e uma frase gravada em 2026 continuaria aparecendo depois de o texto ter sido reescrito. Código desconhecido deve ser exibido CRU, nunca omitido — a linha que some leva junto a informação de que ela existia."
                      },
                      "eixo": {
                        "type": "string",
                        "description": "O eixo do briefing que originou o motivo (`ticket`, `mercado`, `tangibilidade`, `natureza`). **Pode vir vazio**: há motivos sem eixo, como `genero_nao_presumido` e `custo_acima_da_mediana`."
                      }
                    }
                  }
                },
                "ressalvas": {
                  "type": "array",
                  "description": "O que pesou contra. **A rede em PRIMEIRO lugar também costuma trazer ressalvas, e isso não é defeito** — primeira colocada sem nenhuma ressalva é sinal de repertório pobre, não de rede perfeita.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "codigo": {
                        "type": "string",
                        "description": "Código fechado, traduzido na tela. É CÓDIGO e não frase: a frase muda, e uma frase gravada em 2026 continuaria aparecendo depois de o texto ter sido reescrito. Código desconhecido deve ser exibido CRU, nunca omitido — a linha que some leva junto a informação de que ela existia."
                      },
                      "eixo": {
                        "type": "string",
                        "description": "O eixo do briefing que originou o motivo (`ticket`, `mercado`, `tangibilidade`, `natureza`). **Pode vir vazio**: há motivos sem eixo, como `genero_nao_presumido` e `custo_acima_da_mediana`."
                      }
                    }
                  }
                }
              }
            }
          },
          "termos": {
            "type": "array",
            "description": "Termos de segmentação para procurar no Gerenciador de cada plataforma.\n\n**Vêm nas DUAS origens.** Num público montado eles saem do briefing; num semelhante eles são os termos de segmentação de PÚBLICO SEMELHANTE — o que procurar no Gerenciador depois de subir o arquivo. Um import que só entrega download é um download, e não um público.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "eixo": {
                  "type": "string",
                  "enum": [
                    "interesse",
                    "comportamento",
                    "demografia",
                    "cargo",
                    "busca",
                    "semelhante"
                  ]
                },
                "sementes": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "Mapa `plataforma → texto a digitar na busca de segmentação dela`.\n\n**São TEXTOS, nunca identificadores de interesse.** Resolver o `id` que a Meta ou o Google usa exige a API de busca de segmentação de cada uma, que não é chamada aqui — e inventar um `id` seria a pior coisa que este produto poderia fazer.\n\n**Plataforma AUSENTE do mapa significa que não há equivalente honesto lá.** A ausência é informação: ela não é preenchida com string vazia."
                },
                "sinonimos": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Palavras que, citadas pelo cliente, fazem este termo subir. Usadas só para ordenar."
                }
              }
            }
          },
          "lista": {
            "$ref": "#/components/schemas/ListaDeContatos"
          },
          "qualidade": {
            "$ref": "#/components/schemas/QualidadeDaLista"
          },
          "exportacoes": {
            "type": "array",
            "description": "Uma entrada por plataforma com formato de exportação implementado, em ordem fixa: `meta`, `google`, `tiktok`. **Só em `origem: semelhante`.**",
            "items": {
              "type": "object",
              "properties": {
                "plataforma": {
                  "type": "string",
                  "enum": [
                    "meta",
                    "google",
                    "tiktok"
                  ]
                },
                "minimo": {
                  "type": "integer",
                  "description": "O piso publicado da plataforma, comparado contra `aproveitadas`."
                },
                "aproveitadas": {
                  "type": "integer",
                  "description": "Repete `qualidade.aproveitadas`, para a comparação ficar lado a lado."
                },
                "serve": {
                  "type": "boolean",
                  "description": "`aproveitadas >= minimo`."
                }
              }
            }
          },
          "pode_exportar": {
            "type": "boolean",
            "description": "**É UX, não controle.** Ele existe para a tela não desenhar um botão que vai responder 403, e é calculado contra o token DESTA requisição. Quem decide o download é o servidor, que exige `publico.exportar` na própria rota de exportação — o endereço é digitável, e é o tipo de coisa que alguém manda num chat.\n\nSó em `origem: semelhante`: um público montado não tem arquivo para exportar, e pedir a exportação dele responde **409 `ORIGEM_INCOMPATIVEL`** — nunca 404 (o público existe e está na tela de quem pediu) e nunca 400 (a requisição está inteiramente correta)."
          }
        }
      },
      "ListaDeContatos": {
        "type": "object",
        "description": "O cabeçalho da base que originou um público `semelhante`. **Nenhum contato vem aqui**, nem resumido.\n\nA lista é a unidade de BASE LEGAL e de RETENÇÃO: `base_legal`, `retencao_dias` e `expira_em` valem para todos os contatos dela, e é por isso que apagar o público apaga os contatos junto.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "base_legal": {
            "type": "string",
            "enum": [
              "consentimento",
              "contrato",
              "legitimo_interesse"
            ],
            "description": "Escolhida ANTES do primeiro byte subir. Base legal escolhida depois do upload é justificativa, não fundamento."
          },
          "retencao_dias": {
            "type": "integer",
            "minimum": 1,
            "maximum": 730,
            "description": "Prazo declarado, em dias. O piso de 1 existe para que zero não vire, por engano de tela, uma lista que nunca vence."
          },
          "expira_em": {
            "type": "string",
            "format": "date-time",
            "description": "Quando a lista deve ser apagada. É GRAVADO, não derivado da leitura — quem apaga é uma rotina de retenção, e ela não deve reimplementar a regra de prazo.\n\n**Mudar `retencao_dias` não move este campo sozinho.** Prorrogar é escrever os dois, e isso é decisão consciente em vez de efeito colateral."
          },
          "criado_por": {
            "type": "string",
            "description": "O sujeito do token verificado de quem subiu a lista. Nunca vem do corpo. Numa tabela com base legal declarada, 'quem subiu isto' é a primeira pergunta de qualquer apuração."
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "QualidadeDaLista": {
        "type": "object",
        "description": "O que a importação encontrou no arquivo. São contagens de LINHA, e toda decisão sobre a base sai daqui.",
        "properties": {
          "linhas_lidas": {
            "type": "integer",
            "description": "Total de linhas do arquivo original."
          },
          "aproveitadas": {
            "type": "integer",
            "description": "Linhas que viraram contato único. **NÃO É ALCANCE** — ver o cabeçalho deste arquivo."
          },
          "duplicadas": {
            "type": "integer",
            "description": "Descartadas por repetir a chave. Proporção alta quase sempre denuncia exportação bagunçada de CRM, não base ruim."
          },
          "sem_identificador": {
            "type": "integer",
            "description": "Linhas sem e-mail nem telefone. Proporção alta quase sempre revela COLUNA MAPEADA ERRADA — nome e cidade sozinhos não casam ninguém em plataforma nenhuma."
          },
          "com_email": {
            "type": "integer"
          },
          "com_telefone": {
            "type": "integer"
          },
          "com_nome": {
            "type": "integer"
          },
          "avisos": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "mapeamento_provavelmente_errado",
                "abaixo_do_minimo_de_todas",
                "serve_so_na_meta",
                "acima_do_minimo_abaixo_do_recomendado",
                "muita_duplicada",
                "so_email",
                "so_telefone"
              ]
            },
            "description": "Códigos, não frases — frase gravada envelhece junto com o texto que a substituiu.\n\n**Dois avisos da MESMA faixa podem sair juntos**: entre 100 e 999 aproveitadas, `serve_so_na_meta` e `acima_do_minimo_abaixo_do_recomendado` vêm os dois. Uma interface que espere um só mostra metade do diagnóstico."
          }
        }
      },
      "PreparoDeLista": {
        "type": "object",
        "description": "A leitura do cabeçalho de um CSV, sem gravar nada. **O arquivo não fica guardado em lugar nenhum entre esta chamada e a importação** — ele sobe duas vezes, e essa é justamente a propriedade que se quer: uma base de clientes não espera confirmação parada num diretório de trabalho.",
        "properties": {
          "cabecalho": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Os nomes das colunas, na ordem do arquivo."
          },
          "mapeamento_sugerido": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Um código de campo por coluna, na MESMA ordem. **A correspondência é POSICIONAL**: a coluna `i` é descrita pelo campo `i`. Casar por nome quebraria no primeiro arquivo com duas colunas homônimas, que é comum em exportação de CRM."
          },
          "linhas": {
            "type": "integer",
            "description": "Quantas linhas o servidor leu para chutar o mapeamento."
          },
          "campos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Os códigos de campo que o mapeamento aceita — os MESMOS de `campos_de_lista` da taxonomia, repetidos aqui para a tela montar os seletores sem uma segunda ida ao servidor."
          },
          "bases_legais": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "consentimento",
                "contrato",
                "legitimo_interesse"
              ]
            },
            "description": "As bases legais aceitas — as MESMAS da taxonomia. Quem integra deve ler de um dos dois e nunca digitar a lista."
          }
        }
      },
      "VocabularioDoPlano": {
        "type": "object",
        "description": "As sugestões de preenchimento do quadro de ações, e a lista dos campos que de fato são FECHADOS.\n\n**Leia `fechados` antes de validar qualquer coisa.** Só o que está nele é enum; todo o resto é texto livre com sugestão, e recusar um valor fora da sugestão recusaria a primeira frase de todo cliente novo.\n\nNão muda com a janela de datas. Muda quando o cliente declara algo novo.",
        "properties": {
          "verbos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Verbos de etapa já declarados por este cliente («Atrair tutores»). SUGESTÃO, em texto livre — não é enum."
          },
          "objetivos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Objetivos de etapa já declarados. SUGESTÃO, texto livre."
          },
          "metricas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Métricas de etapa já declaradas. SUGESTÃO, texto livre. Atenção: é a métrica que o cliente DECLARA perseguir, e ela pode não existir na coleta. Não a case com um campo de `analytics` sem conferir."
          },
          "status": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Os estados possíveis de uma ação — `ideia`, `aprovado`, `rodando`. Este SIM é fechado (ver `fechados`), e vem como CÓDIGO: a frase é da interface, que precisa existir em mais de um idioma."
          },
          "canais": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "O catálogo de canais de execução oferecido como sugestão (`Instagram`, `WhatsApp`, `Kommo Salesbot`, `n8n + WhatsApp`, `Omnibees`…).\n\n**Não confunda com o canal de `plano.marcacao`**, que é um enum de sete redes de anúncio e serve para conciliar gasto. Metade destes não é rede de mídia e não concilia nada."
          },
          "canais_declarados": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Os canais que ESTE cliente já escreveu, fora do catálogo. Somam-se a `canais` na sugestão."
          },
          "unidades_de_negocio": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "As unidades de negócio já declaradas por este cliente. SUGESTÃO — uma unidade nova é aceita, e é assim que a primeira delas nasce."
          },
          "fechados": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Os nomes dos campos que são LISTA FECHADA. Hoje, `[\"status\"]`.\n\nEle existe para que a interface (e a sua integração) não crave a decisão: o dia em que `canal` fechar, ou `status` abrir, quem lê este campo acompanha sem mudar uma linha."
          }
        }
      },
      "AcaoDeMarketing": {
        "type": "object",
        "description": "Uma ação de marketing DECLARADA por uma pessoa e pendurada numa etapa do funil. Ela não tem gasto associado e não sai de plataforma nenhuma: é o trabalho que o cliente combinou fazer dentro daquela etapa.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "etapa_id": {
            "type": "string",
            "format": "uuid",
            "description": "A etapa do funil onde ela está. É o eixo das COLUNAS do quadro — e não o estado do trabalho, que é `status`."
          },
          "titulo": {
            "type": "string",
            "description": "Até 120 caracteres."
          },
          "descricao": {
            "type": "string",
            "description": "Até 1.200 caracteres. Pode vir vazia."
          },
          "canal": {
            "type": "string",
            "description": "O canal de EXECUÇÃO da ação, em texto livre até 60 caracteres — `Instagram`, `WhatsApp`, `Kommo Salesbot`, `n8n + WhatsApp`.\n\n**Não confunda com o canal de `plano.marcacao`**, que é um enum de sete redes de anúncio e serve para conciliar gasto. Este aqui não concilia nada: metade dos valores reais não é rede de mídia."
          },
          "unidade_de_negocio": {
            "type": "string",
            "description": "A unidade FÍSICA ou comercial do cliente a que a ação se aplica (uma loja, uma filial, uma casa), em texto livre até 60 caracteres. Vazio significa «todas».\n\n**Não confunda com a unidade de ATRIBUIÇÃO** — campanha, conjunto ou anúncio — das rotas de relatório. São dois sentidos da mesma palavra, e é por isso que o campo tem este nome comprido.\n\nEla NÃO reparte gasto: para um cliente com campanha unificada, investimento por unidade não existe, e declarar a intenção («quero trazer hóspede para a Serra») nunca vira rateio do realizado."
          },
          "status": {
            "type": "string",
            "enum": [
              "ideia",
              "aprovado",
              "rodando"
            ],
            "description": "Em que pé a ação está. **Não são os estados do quadro de tarefas** (`aberta`/`fazendo`/`feita`/`arquivada`): lá o eixo das colunas é o estado; aqui o eixo é a etapa, e o estado é um selo dentro do cartão."
          },
          "posicao": {
            "type": "number",
            "format": "double",
            "description": "A ordem dentro da coluna. É ponto flutuante DE PROPÓSITO: mover é gravar a média entre os dois vizinhos, o que escreve uma linha em vez de renumerar a coluna inteira. Não presuma que seja inteiro, nem que seja contíguo, nem que comece em zero — ordene por ele e ignore o valor."
          },
          "campos": {
            "type": "object",
            "additionalProperties": true,
            "description": "Valores dos CAMPOS PERSONALIZADOS da ação (ADR-0040): `{<id da definição>: valor}`, sempre objeto (vazio quando não há nenhum). Escrita é mescla: chave presente troca, `null` apaga, ausente não toca. As definições vêm de `GET /campos-personalizados`."
          }
        }
      },
      "EtapaComAcoes": {
        "type": "object",
        "description": "Uma etapa do funil desenhado, com o que o cliente declarou sobre ela e as ações penduradas nela.\n\n**Apagar uma etapa que tem ação responde 409 `ETAPA_COM_ACOES` e não apaga nada.** É `ON DELETE RESTRICT` de propósito: cascata sumiria com cartões em silêncio ao tirar uma caixa do desenho do funil. O caminho é mover ou apagar as ações primeiro.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "ordem": {
            "type": "integer",
            "description": "A posição da etapa no funil, do topo para o fundo. Ordene por ela — a ordem do array não é contrato."
          },
          "verbo": {
            "type": "string",
            "description": "O que a etapa FAZ com quem passa por ela, em texto livre até 40 caracteres («Atrair tutores»). Vazio = não declarado.\n\n**Os três campos declarados se gravam JUNTOS**, e vazio APAGA: `PUT /plano/etapas/{id}` é substituição, não remendo."
          },
          "objetivo": {
            "type": "string",
            "description": "O resultado que a etapa persegue, em texto livre até 300 caracteres. Vazio = não declarado."
          },
          "metrica": {
            "type": "string",
            "description": "A métrica que o cliente DECLARA perseguir na etapa, em texto livre até 120 caracteres. Ela pode não existir na coleta — não a case com um campo de `analytics` sem conferir. Vazio = não declarado."
          },
          "acoes": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/AcaoDeMarketing"
            },
            "description": "As ações desta etapa. Vem `null` quando não há nenhuma — e `null` aqui significa exatamente «nenhuma», nunca «não consegui ler»."
          }
        }
      },
      "QuadroDeAcoes": {
        "type": "object",
        "description": "O quadro completo. Uma etapa sem ação continua vindo, porque ela continua sendo uma coluna do quadro — omiti-la faria a tela desenhar um funil com um buraco no meio.",
        "properties": {
          "etapas": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/EtapaComAcoes"
            }
          }
        }
      },
      "PontoDaSerie": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Totais"
          },
          {
            "type": "object",
            "properties": {
              "date": {
                "type": "string",
                "format": "date",
                "description": "O dia, no fuso da CONTA de anúncio e não em UTC. É por isso que o total de um mês pode divergir da soma de trinta dias vindos de contas em fusos diferentes."
              }
            }
          }
        ]
      },
      "Campanha": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Totais"
          },
          {
            "type": "object",
            "properties": {
              "campaign_id": {
                "type": "string",
                "description": "O identificador na plataforma. É a ÚNICA chave estável: nome de campanha muda, e casar por nome já produziu atribuição errada neste sistema quando dois clientes usaram o mesmo nome."
              },
              "campaign_name": {
                "type": "string"
              },
              "provider": {
                "type": "string",
                "description": "Rede de anúncio da campanha."
              },
              "budget_kind": {
                "type": "string",
                "enum": [
                  "daily",
                  "lifetime",
                  "none"
                ],
                "description": "Onde mora o orçamento: na campanha (`daily`/`lifetime`) ou nos conjuntos (`none`)."
              },
              "account_id": {
                "type": "string",
                "description": "Conta de anúncio de origem."
              },
              "days": {
                "type": "integer",
                "description": "Quantos dias do período tiveram entrega desta campanha."
              }
            }
          }
        ]
      },
      "Conta": {
        "type": "object",
        "description": "Uma conta de anúncio do cliente. O par `last_date` / `last_observed_at` é o que distingue 'a campanha caiu' de 'a coleta parou' — a segunda hipótese é a mais comum e a que ninguém checa.",
        "properties": {
          "account_id": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "meta",
              "google",
              "tiktok"
            ],
            "description": "Rede de anúncio de origem."
          },
          "name": {
            "type": "string",
            "description": "Nome da conta na plataforma. Cai no identificador quando a estrutura ainda não foi lida — nunca vem vazio."
          },
          "timezone": {
            "type": "string",
            "description": "Fuso em que os dias desta conta são contados."
          },
          "last_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Último DIA com dado. Nulo quando a conta nunca entregou nada."
          },
          "last_observed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Quando a coleta falou com esta conta pela última vez. Diferente de `last_date`: uma conta coletada há dez minutos pode ter dados só até anteontem, e isso é normal."
          },
          "conferida_via": {
            "type": "string",
            "enum": [
              "direta",
              "ga4"
            ],
            "nullable": true,
            "description": "Por onde `last_observed_at` foi conferido: `direta` (trilha da API da própria rede) ou `ga4` (coleta do Google Analytics 4, enquanto a API do Google Ads não está liberada). Nulo quando a conta nunca foi conferida."
          }
        }
      },
      "ConversaoTotal": {
        "type": "object",
        "description": "O total de uma categoria de conversão. Só entram ações canônicas: a mesma venda reportada por mais de um tipo de ação não é contada duas vezes.",
        "properties": {
          "kind": {
            "type": "string",
            "description": "Como este cliente classificou a ação. O que estiver como não classificado NÃO entra em total nenhum, de propósito."
          },
          "count_micros": {
            "type": "integer",
            "format": "int64",
            "description": "Volume, em micros — divida por 1.000.000 em MICROS: divida por 1.000.000 para obter o valor na moeda. 1.500.000 é 1,50. Nunca use ponto flutuante para converter — multiplique e divida como inteiro."
          },
          "value_micros": {
            "type": "integer",
            "format": "int64",
            "description": "Valor atribuído em MICROS: divida por 1.000.000 para obter o valor na moeda. 1.500.000 é 1,50. Nunca use ponto flutuante para converter — multiplique e divida como inteiro."
          },
          "currency": {
            "type": "string",
            "description": "Moeda ISO-4217."
          },
          "outcome": {
            "type": "boolean",
            "description": "Verdadeiro para resultado de negócio (compra, lead, conversa); falso para métrica de meio de caminho (engajamento, clique, vídeo)."
          },
          "types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Os tipos de ação, com o nome da rede, que compõem o total."
          }
        }
      },
      "ConversaoDetalhe": {
        "type": "object",
        "description": "Uma linha do detalhe por tipo de ação, com o papel que explica por que ela soma ou não.",
        "properties": {
          "action_type": {
            "type": "string",
            "description": "O nome da ação na plataforma."
          },
          "kind": {
            "type": "string",
            "description": "Como este cliente classificou a ação."
          },
          "role": {
            "type": "string",
            "description": "O papel da linha no total (ex.: canônica)."
          },
          "count_micros": {
            "type": "integer",
            "format": "int64",
            "description": "Volume, em micros — divida por 1.000.000 em MICROS: divida por 1.000.000 para obter o valor na moeda. 1.500.000 é 1,50. Nunca use ponto flutuante para converter — multiplique e divida como inteiro."
          },
          "value_micros": {
            "type": "integer",
            "format": "int64",
            "description": "Valor atribuído em MICROS: divida por 1.000.000 para obter o valor na moeda. 1.500.000 é 1,50. Nunca use ponto flutuante para converter — multiplique e divida como inteiro."
          }
        }
      },
      "Revisao": {
        "type": "object",
        "description": "Uma alteração retroativa: a plataforma mudou um número já reportado.",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "campaign_id": {
            "type": "string"
          },
          "campaign_name": {
            "type": "string"
          },
          "field": {
            "type": "string",
            "description": "O campo que mudou."
          },
          "previous": {
            "type": "string",
            "nullable": true
          },
          "current": {
            "type": "string",
            "nullable": true
          },
          "delta": {
            "type": "integer",
            "format": "int64",
            "nullable": true
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          },
          "lag_days": {
            "type": "integer",
            "description": "Dias entre o fato e a observação da mudança."
          },
          "restatement": {
            "type": "boolean",
            "description": "Verdadeiro para reapuração retroativa; falso para acúmulo do dia corrente."
          },
          "reason": {
            "type": "string",
            "description": "A causa provável da alteração."
          }
        }
      },
      "Resumo": {
        "type": "object",
        "description": "O agregado devolvido por `/summary`.",
        "properties": {
          "tenant": {
            "type": "object",
            "description": "O cliente do token."
          },
          "role": {
            "type": "string"
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "omitted": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Permissões que a chave não tem; os blocos delas vêm vazios."
          },
          "since": {
            "type": "string",
            "format": "date"
          },
          "until": {
            "type": "string",
            "format": "date"
          },
          "points": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PontoDaSerie"
            }
          },
          "campaigns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Campanha"
            }
          },
          "accounts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Conta"
            }
          },
          "revisions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Revisao"
            }
          },
          "pacing": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "restatements": {
            "type": "integer",
            "format": "int64"
          },
          "total_revisions": {
            "type": "integer",
            "format": "int64"
          },
          "conversions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversaoTotal"
            }
          },
          "conversion_details": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversaoDetalhe"
            }
          },
          "caveats": {
            "type": "object",
            "description": "Selos de qualidade do dado."
          }
        }
      },
      "Erro": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Código estável, seguro para comparar."
              },
              "message": {
                "type": "string",
                "description": "Texto para pessoa. Pode mudar entre versões — não decida nada a partir dele."
              },
              "trace_id": {
                "type": "string",
                "description": "Identificador da ocorrência. Cite-o ao pedir suporte."
              }
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "tokenDeAcesso": []
    }
  ],
  "paths": {
    "/v1/token": {
      "post": {
        "summary": "Trocar a chave de API por um token de acesso",
        "description": "O primeiro passo de qualquer integração. A chave (`adv_...`) vai no cabeçalho `Authorization` — **nunca** no corpo nem na URL: corpo de requisição aparece em log de aplicação com frequência, e query string aparece em log de proxy sempre.\n\nO token devolvido vale **dois minutos**. Renove quando expirar; a resposta traz `expires_at`.\n\nEsta é a única rota que aceita a chave. Todas as outras exigem o token.",
        "security": [
          {
            "chaveDeAPI": []
          }
        ],
        "responses": {
          "200": {
            "description": "Token emitido",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "tenant_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "O cliente desta chave. Use-o no caminho das rotas de leitura — ele não é escolhido por você."
                    },
                    "tenant_name": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "viewer",
                        "analyst"
                      ]
                    },
                    "permissions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "accounts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "As contas de anúncio que esta chave enxerga. `[\"*\"]` significa todas as do cliente."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Chave ausente, malformada, vencida ou revogada. A resposta NÃO distingue os quatro casos: fazê-lo contaria a quem tenta se ele acertou o formato e se a chave já existiu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/summary": {
      "get": {
        "summary": "Totais do período",
        "description": "O agregado da tela inicial numa resposta só: série diária (`points`), campanhas, contas, conversões, revisões e ritmo de orçamento da janela pedida. É a rota por onde começar.\n\n**Blocos restritos são omitidos, não negados.** Os que o papel da chave não alcança (`account.read`, `audit.read`, `budget.read`) vêm vazios e listados em `omitted`: lista vazia não prova ausência de dado.\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Omitido, o servidor escolhe uma janela padrão — não presuma qual: peça explicitamente sempre que o número for comparado com outro."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD. Inclusivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Resumo"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/insights/daily": {
      "get": {
        "summary": "Série diária",
        "description": "Os mesmos totais, dia a dia. Some os dias e você obtém o resumo — as duas rotas nunca divergem, porque partem dos mesmos dados.\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Omitido, o servidor escolhe uma janela padrão — não presuma qual: peça explicitamente sempre que o número for comparado com outro."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD. Inclusivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "since": {
                      "type": "string",
                      "format": "date",
                      "description": "Primeiro dia efetivamente usado."
                    },
                    "until": {
                      "type": "string",
                      "format": "date",
                      "description": "Último dia efetivamente usado, inclusivo."
                    },
                    "points": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PontoDaSerie"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/campaigns": {
      "get": {
        "summary": "Desempenho por campanha",
        "description": "Uma linha por campanha com entrega no período.\n\n**Não tire média de métrica derivada entre linhas.** O CPA do conjunto é a soma dos gastos dividida pela soma das conversões — a média dos CPAs individuais dá um número que campanha nenhuma teve, e a diferença cresce com a desigualdade entre elas.\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Omitido, o servidor escolhe uma janela padrão — não presuma qual: peça explicitamente sempre que o número for comparado com outro."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD. Inclusivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "since": {
                      "type": "string",
                      "format": "date",
                      "description": "Primeiro dia efetivamente usado."
                    },
                    "until": {
                      "type": "string",
                      "format": "date",
                      "description": "Último dia efetivamente usado, inclusivo."
                    },
                    "campaigns": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Campanha"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/busca-organica": {
      "get": {
        "summary": "Busca orgânica (Search Console)",
        "description": "O painel do Google Search Console do cliente na janela pedida: totais, série diária, top 50 consultas, top 50 páginas e o total das consultas anonimizadas pelo Google. CTR e posição vêm em milésimos (`ctr_milesimos` = cliques/impressões × 1000) e `sem_impressoes` marca a divisão por zero. As datas são as do horário do Pacífico, como o Google as informa; a série só traz os dias que têm linha.\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "de",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD (Pacífico)."
          },
          {
            "name": "ate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD, inclusivo."
          },
          {
            "name": "propriedade",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Restringe a uma propriedade do cliente. Omitido, soma todas."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/google-analytics": {
      "get": {
        "summary": "Google Analytics 4 (tráfego por canal)",
        "description": "O painel do Google Analytics 4 do cliente na janela pedida: totais, série diária e grupos de canal (mais sessões primeiro, até 50), com sessões, usuários ativos, novos usuários e eventos-chave. A `receita` é uma lista com UMA entrada por moeda, em micros inteiros (1 unidade = 1.000.000): moedas diferentes nunca são somadas. Usuários são somas por dia, canal, origem e mídia, e não usuários únicos. Só agregados, sem dado pessoal. Sem `de`/`ate`: os últimos 30 dias (America/Sao_Paulo); janela máxima de 400 dias. Erros 400: `GA_DATA_INVALIDA`, `GA_JANELA_INVERTIDA`, `GA_JANELA_LONGA`. Cliente que nunca conectou recebe 200 com `conectado: false`.\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "de",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Padrão: 29 dias antes de `ate`."
          },
          {
            "name": "ate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD, inclusivo. Padrão: hoje em America/Sao_Paulo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/google-business": {
      "get": {
        "summary": "Google Business Profile (buscas, mapa, ações e avaliações)",
        "description": "O painel do Google Business Profile do cliente na janela pedida: uma série diária por métrica (impressões na busca e no mapa em desktop e celular, cliques no site, ligações, pedidos de rota, conversas, reservas), totais, as avaliações novas por dia e o retrato (nota média em milésimos, ponderada pelo total de cada local) e os locais com dado. Contagens somam entre os locais. Sem `inicio`/`fim`: os últimos 30 dias (America/Sao_Paulo); janela máxima de 400 dias. Erros 400: `GBP_DATA_INVALIDA`, `GBP_JANELA_INVERTIDA`, `GBP_JANELA_LONGA`, `GBP_LOCAL_INVALIDO`. Cliente que nunca conectou recebe 200 com `conectado: false`.\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "inicio",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Padrão: 29 dias antes de `fim`."
          },
          {
            "name": "fim",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD, inclusivo. Padrão: hoje em America/Sao_Paulo."
          },
          {
            "name": "local",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^locations/[0-9]{1,20}$"
            },
            "description": "Restringe a um local (`locations/N`). Omitido, soma todos."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/merchant-center": {
      "get": {
        "summary": "Google Merchant Center (desempenho por canal e saúde do catálogo)",
        "description": "O painel do Merchant Center do cliente: desempenho por canal (anúncios do Shopping e listagens gratuitas) e por dia (impressões, cliques, conversões em milésimos; conversão ausente é `null`, nunca zero), o ranking de produtos e o retrato do catálogo (aprovados, aprovados em parte, pendentes, reprovados, expirando em 7 dias, por destino e país, e os principais problemas). O CTR não vem da API: a tela o calcula (cliques ÷ impressões). Janela máxima de 400 dias; o retrato é o do dia, não o do período. Cliente que nunca conectou recebe 200 com `conectado: false`.\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "inicio",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Padrão: 29 dias antes de `fim`."
          },
          {
            "name": "fim",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD, inclusivo. Padrão: hoje em America/Sao_Paulo."
          },
          {
            "name": "conta",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^accounts/[0-9]{1,20}$"
            },
            "description": "Restringe a uma conta Merchant (`accounts/N`). Omitido, soma todas."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/sugestoes-de-desempenho": {
      "get": {
        "summary": "Sugestões de desempenho (ROAS real × alvo)",
        "description": "Compara o ROAS real do período (receita ÷ gasto, SOMADOS — nunca média de ROAS) ao ROAS alvo: o `roas_desejado` dos Dados do projeto (`origem_do_alvo: projeto`), senão a premissa `roas_alvo` do plano vigente que cobre o período (`plano`), senão o padrão do segmento (`segmento`, com `acao_da_meta` convidando a defini-la). A RECEITA vem de uma hierarquia de fontes com confiança explícita (`fonte_da_receita`): vendas conciliadas do commerce (alta), receita de compra do GA4 nos canais pagos, bruta menos reembolso (média; baixa se o detector de GA4 desconfigurado achar problema) ou valor de conversão das plataformas (baixa, com aviso de dupla contagem entre redes); o bloco traz o porquê, os avisos e o que falta para subir de confiança. `situacao`: `abaixo` (< 85% do alvo), `normal` (85% a 115%), `acima` (> 115%) ou `incerto` (nenhuma fonte de receita serve — com o `motivo`, nunca 500; havendo gasto, as `sugestoes` são de base: CPA contra o `cpa_maximo`, campanha sem conversão e concentração de gasto, e `sugestoes_de_base` é true). Cada sugestão tem chave estável `roas:<situacao>:<acao>` ou `base:<tema>:<acao>`, que vira `dica_chave` ao virar tarefa. Sem `de`/`ate`: os últimos 30 dias (America/Sao_Paulo).\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "de",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD."
          },
          {
            "name": "ate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD, inclusivo. Janela máxima de 180 dias."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/accounts": {
      "get": {
        "summary": "Contas de anúncio e frescor do dado",
        "description": "As contas do cliente com a data do último dado recebido.\n\n**Consulte isto antes de concluir que algo caiu.** Uma conta que parou de coletar produz exatamente o mesmo desenho de uma campanha que morreu, e as duas hipóteses pedem ações opostas.\n\nExige a permissão `account.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "since": {
                      "type": "string",
                      "format": "date",
                      "description": "Primeiro dia efetivamente usado."
                    },
                    "until": {
                      "type": "string",
                      "format": "date",
                      "description": "Último dia efetivamente usado, inclusivo."
                    },
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Conta"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/conexoes": {
      "get": {
        "summary": "Estado das credenciais de rede do cliente",
        "description": "Uma linha por credencial (rede + login externo): `status` (`active`, `expired`, `revoked`, `error`), `expira_em` e `login_externo` quando houver. **Nunca devolve token nem coluna cifrada.** Conectar é pelo botão Conectar em Contas, no painel; não há rota de escrita.\n\nExige a permissão `account.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "conexoes": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/conversions": {
      "get": {
        "summary": "O que está sendo contado como conversão",
        "description": "Os totais por categoria (`conversions`) e o detalhe por tipo de ação reportado pela plataforma (`conversion_details`, com o papel que explica se a linha soma ou não). O que não estiver classificado não entra em total nenhum.\n\nExige a permissão `insights.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Omitido, o servidor escolhe uma janela padrão — não presuma qual: peça explicitamente sempre que o número for comparado com outro."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD. Inclusivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "since": {
                      "type": "string",
                      "format": "date",
                      "description": "Primeiro dia efetivamente usado."
                    },
                    "until": {
                      "type": "string",
                      "format": "date",
                      "description": "Último dia efetivamente usado, inclusivo."
                    },
                    "conversions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConversaoTotal"
                      }
                    },
                    "conversion_details": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConversaoDetalhe"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/audit/revisions": {
      "get": {
        "summary": "Alterações retroativas",
        "description": "Números que a plataforma mudou DEPOIS de já os ter reportado.\n\nÉ a rota que responde 'esse número estava diferente ontem'. Se você guarda histórico do nosso dado, reconcilie por aqui — sem isso, o seu banco vai divergir do nosso sem que nada acuse.\n\nExige a permissão `audit.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            },
            "description": "Quantas alterações devolver, das mais recentes. Esta rota NÃO lê `since`/`until`."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revisions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Revisao"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/pacing": {
      "get": {
        "summary": "Ritmo de consumo do orçamento",
        "description": "Quanto do orçamento contratado do mês já foi gasto e se está no ritmo. Os limiares são do servidor — recalculá-los do seu lado faria o seu alerta e o nosso painel discordarem sobre a mesma situação.\n\nExige a permissão `budget.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "on",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Data de referência, AAAA-MM-DD. Omitida, vale hoje (UTC)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "on": {
                      "type": "string",
                      "format": "date"
                    },
                    "pacing": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/plano/funil": {
      "get": {
        "summary": "Funil de mídia declarado",
        "description": "O gasto agrupado por etapa do funil, por produto e por canal, segundo o que a equipe marcou.\n\n**As três leituras somam o mesmo total e não se somam entre si.** Existem dois baldes especiais: `não marcadas`, o gasto sem classificação, e `vários canais`, a campanha declarada em mais de um canal — que não é rateada porque a plataforma não informa a divisão, e inventar a proporção seria pior que agrupar.\n\nExige a permissão `plano.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Omitido, o servidor escolhe uma janela padrão — não presuma qual: peça explicitamente sempre que o número for comparado com outro."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD. Inclusivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/plano/vocabulario": {
      "get": {
        "summary": "O vocabulário fechado das etapas do funil",
        "description": "Os códigos de verbo, objetivo e métrica que uma etapa pode declarar.\n\n**São códigos, não rótulos.** A frase que aparece na tela é da interface, porque ela existe em mais de um idioma — um rótulo em português servido daqui apareceria em português numa tela aberta em inglês, e nada reprovaria.\n\nMesma resposta para todo cliente; não muda com a janela de datas.\n\nExige a permissão `plano.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VocabularioDoPlano"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/plano/acoes": {
      "get": {
        "summary": "O quadro de ações: as etapas do funil com o trabalho dentro",
        "description": "As etapas do funil desenhado, em ordem, cada uma com o vocabulário declarado e as ações de marketing penduradas nela.\n\n**O eixo das colunas é a ETAPA, e não o estado do trabalho.** É a diferença entre este quadro e o de tarefas: aqui o cliente redesenha as colunas quando redesenha o funil, e o estado (`ideia`/`aprovado`/`rodando`) é um selo dentro do cartão.\n\n**Nada aqui tem dinheiro.** A ação é DECLARAÇÃO de trabalho a fazer; o gasto por etapa está em `/plano/funil`, e os dois não se somam nem se conferem — uma ação `rodando` pode não ter um real marcado.\n\nOrdene as etapas por `ordem` e as ações por `posicao`; a ordem do array não é contrato.\n\nExige a permissão `plano.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuadroDeAcoes"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/publicos/taxonomia": {
      "get": {
        "summary": "As listas fechadas do construtor de público",
        "description": "Os cinco eixos aceitos, os limites de tamanho, as bases legais e os pisos de cada plataforma. É a mesma resposta para todo cliente e ela não muda com a janela de datas.\n\n**Leia esta rota antes de montar um briefing.** Os eixos são validados pelo servidor: valor fora destas listas é recusado na gravação.\n\nEla também serve o IMPORTADOR: `campos_de_lista` é o vocabulário do mapeamento de colunas, e `bases_legais` é o do seletor de base legal.\n\nExige a permissão `publico.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaxonomiaDePublico"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/publicos/redes": {
      "get": {
        "summary": "O perfil médio das dez redes",
        "description": "Para onde a base de cada rede pende quando nada é dito, com os índices de custo.\n\n**Duas leituras erradas, e as duas custam dinheiro.** O perfil é MÉDIO: descreve o ponto de partida de quem não segmentou, não uma regra sobre um anúncio. E `custo_cpm_relativo` é um ÍNDICE em que `1,00` é a mediana das dez — não reais, não previsão. Tratá-lo como preço é como um orçamento nasce errado.\n\nCatálogo curado, servido do código: não vem de plataforma nenhuma e não muda com a janela.\n\nExige a permissão `publico.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PerfilDeRede"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/publicos": {
      "get": {
        "summary": "O inventário ÚNICO de públicos do cliente",
        "description": "Todos os públicos, **das duas origens misturadas**: os `montado` (um briefing respondido em quatro passos) e os `semelhante` (uma lista de clientes importada). Separá-los em duas listas reconstruiria as duas áreas que a unificação desfez — do lado de quem usa, as duas produzem a mesma coisa: um público para levar ao Gerenciador.\n\n**A proposta NÃO vem aqui** — nem o recorte, nem o ranking de redes, nem os termos, nem o relatório de qualidade. Cada linha traz um `resumo` PARCIAL, com o que aquela origem tem de próprio, e **campo ausente é ausente**: um montado não traz `aproveitadas`, e ele não vem como zero.\n\nExige a permissão `publico.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "publicos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PublicoNaLista"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/publicos/{id}": {
      "get": {
        "summary": "Um público inteiro, seja qual for a origem dele",
        "description": "O cabeçalho comum mais os blocos que AQUELA origem tem.\n\n**Desenhe pela PRESENÇA de cada bloco, nunca pelo valor de `origem`.** Um `montado` traz `briefing`, `sugestao`, `redes` e `termos`; um `semelhante` traz `lista`, `qualidade`, `exportacoes` e `termos`. Uma origem nova não deve quebrar quem só sabia das duas.\n\n**Só a declaração existe no banco.** A proposta é recalculada a cada leitura, a partir do repertório vigente — a mesma requisição pode devolver outro ranking amanhã sem ninguém ter editado nada. É de propósito: uma sugestão gravada continuaria recomendando o que o catálogo já desaprendeu, e sem sintoma nenhum, porque a linha estaria lá, íntegra, apenas velha.\n\nSe você guarda esta resposta, **não trate a diferença entre duas leituras como mudança do cliente**: compare `publico`, `briefing` e `lista`, que são as partes declaradas.\n\n**Nenhum modelo de linguagem participa disto.** As regras são determinísticas e o texto livre do briefing entra apenas como palavra-chave para ordenar os termos.\n\n**Continua sem nenhum contato**, nem resumido. Os resumos só saem pela exportação, que exige `publico.exportar` e não está nesta especificação.\n\nExige a permissão `publico.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O recurso. Um identificador que exista, mas pertença a outro cliente, responde 404 — não 403, e não uma resposta vazia."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicoCompleto"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    },
    "/v1/t/{tenantID}/creatives": {
      "get": {
        "summary": "Galeria de criativos com desempenho",
        "description": "As peças que rodaram, com os números de cada uma. A imagem vem por URL pública do nosso armazenamento — **não a guarde**: ela aponta para um objeto endereçado por conteúdo, e o endereço muda se a peça mudar.\n\nExige a permissão `creative.read`.",
        "parameters": [
          {
            "name": "tenantID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "O cliente. Vem SEMPRE do caminho — nunca de cabeçalho, nunca de cookie. Ele é conferido contra o token: um identificador que não bata com o do token devolve 403, e não uma resposta vazia."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Primeiro dia, AAAA-MM-DD. Omitido, o servidor escolhe uma janela padrão — não presuma qual: peça explicitamente sempre que o número for comparado com outro."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Último dia, AAAA-MM-DD. Inclusivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Token ausente, malformado ou vencido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          },
          "403": {
            "description": "O token é válido, e o escopo não. Ou ele foi emitido para outro cliente, ou o papel não inclui a permissão da rota. NÃO é 404 de propósito: esconder um erro de escopo atrás de 'não encontrado' faz quem integra procurar o problema no lugar errado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                }
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "leitura",
      "description": "Tudo nesta especificação é leitura. As rotas de escrita existem e não estão publicadas: elas mudam o que o painel mostra ou gastam dinheiro na plataforma do cliente, e as duas coisas exigem uma pessoa confirmando na tela, com trilha."
    }
  ]
}
