{
  "openapi": "3.1.0",
  "info": {
    "title": "API Asender",
    "version": "2026-11-15",
    "summary": "A API pública da plataforma Asender.",
    "description": "Documentação completa em https://developers.asender.net. Para agentes: o contexto inteiro em um arquivo está em https://developers.asender.net/llms-full.txt. Dúvidas de uso do produto: https://developers.asender.net."
  },
  "servers": [
    {
      "url": "https://api.asender.net",
      "description": "Plataforma"
    },
    {
      "url": "https://api.mail.asender.net",
      "description": "E-mail"
    },
    {
      "url": "https://auth.asender.net",
      "description": "Identidade (OIDC / OAuth 2.1)"
    },
    {
      "url": "https://api.crm.asender.net",
      "description": "CRM"
    },
    {
      "url": "https://api.pages.asender.net",
      "description": "Páginas"
    },
    {
      "url": "https://api.pages.asender.net",
      "description": "Páginas"
    }
  ],
  "tags": [
    {
      "name": "api",
      "description": "Contas, membros, convites, API keys e plano — e a mensageria pública por API key. É a API da plataforma, não de um produto."
    },
    {
      "name": "mail",
      "description": "Envio transacional (e OTP) por /v1/emails com API key, gestão de domínios e caixas, mensagens, rascunhos, contatos e assinatura."
    },
    {
      "name": "auth",
      "description": "Authorization Server. É daqui que sai o token que todas as outras APIs exigem."
    },
    {
      "name": "crm_api",
      "description": "Jornadas, alertas, destinos e integrações de conversão. Sistema próprio, endereço próprio."
    },
    {
      "name": "pages_api",
      "description": "Páginas, formulários, redirects, mídia — e a analytics do que foi publicado. Inclui o runtime que serve e mede o publicado."
    },
    {
      "name": "pages_runtime",
      "description": "Páginas, formulários, redirects, mídia — e a analytics do que foi publicado. Inclui o runtime que serve e mede o publicado."
    }
  ],
  "paths": {
    "/v1/analytics/overview": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Visão geral do publicado",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaFiltro"
          },
          {
            "$ref": "#/components/parameters/pages_De"
          },
          {
            "$ref": "#/components/parameters/pages_Ate"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "400": {
            "description": "janela inválida (RFC3339, teto 92 dias)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/analytics/overview?pagina=&de=&ate=.\n\nOnde é usada: tela inicial de analytics.\n\nEfeitos: duas leituras. Funil e atribuição juntos porque a tela mostra os dois lado a lado, e duas requisições poderiam ler janelas diferentes — o total da atribuição não fecharia com o topo do funil, e o número \"errado\" seria o certo.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Analytics.Overview",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/breakdown": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Quebra por dimensão",
        "parameters": [
          {
            "name": "dimensao",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "utm_source"
            },
            "description": "dimensão da quebra; dimensão desconhecida devolve 400 COM a lista aceita"
          },
          {
            "$ref": "#/components/parameters/pages_PaginaFiltro"
          },
          {
            "$ref": "#/components/parameters/pages_De"
          },
          {
            "$ref": "#/components/parameters/pages_Ate"
          },
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "400": {
            "description": "dimensão/janela inválidas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/analytics/breakdown?dimensao=utm_source&…\n\nOnde é usada: tela de origens.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Analytics.Breakdown",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/timeseries": {
      "get": {
        "operationId": "PublicAnalyticsTimeseries",
        "x-asender-authz": "apikey",
        "tags": [
          "public-analytics"
        ],
        "summary": "Série diária (America/Sao_Paulo).",
        "description": "Escopo `analytics:read`. Um ponto por dia com as mesmas contagens do overview; a soma da\nsérie fecha com o total. Janela de até **92 dias**.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "analytics:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Início INCLUSIVO da janela: RFC 3339 ou `YYYY-MM-DD` (= 00:00:00Z). Padrão: `to` menos 30 dias."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Fim EXCLUSIVO da janela (mesmo formato). Padrão: agora."
          },
          {
            "name": "page_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por página."
          },
          {
            "name": "form_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por formulário."
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por origem (utm_source)."
          },
          {
            "name": "medium",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por meio (utm_medium)."
          },
          {
            "name": "campaign",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por campanha (utm_campaign)."
          },
          {
            "name": "country",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por país."
          },
          {
            "name": "device",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por dispositivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataAnalyticsSeries"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "a.Timeseries",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/forms": {
      "get": {
        "operationId": "PublicAnalyticsForms",
        "x-asender-authz": "apikey",
        "tags": [
          "public-analytics"
        ],
        "summary": "Resumo de formulários ou funil de um formulário.",
        "description": "Escopo `analytics:read`. Sem `form_id`: um item por formulário (visualizações, inícios,\nenvios e taxa), janela de até **92 dias**. Com `form_id`: o funil completo do formulário\n(série diária, por origem/dispositivo/página, abandono por campo), janela de até **400 dias**.\nFormulário de outra conta ou inexistente: relatório zerado (a existência não é confirmada).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "analytics:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Itens por página."
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Início INCLUSIVO da janela: RFC 3339 ou `YYYY-MM-DD` (= 00:00:00Z). Padrão: `to` menos 30 dias."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Fim EXCLUSIVO da janela (mesmo formato). Padrão: agora."
          },
          {
            "name": "page_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por página."
          },
          {
            "name": "form_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por formulário."
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por origem (utm_source)."
          },
          {
            "name": "medium",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por meio (utm_medium)."
          },
          {
            "name": "campaign",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por campanha (utm_campaign)."
          },
          {
            "name": "country",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por país."
          },
          {
            "name": "device",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Filtra por dispositivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataAnalyticsForms"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "a.Forms",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/campaigns": {
      "get": {
        "operationId": "PublicListCampaigns",
        "x-asender-authz": "apikey",
        "tags": [
          "public-campaigns"
        ],
        "summary": "Campanhas de e-mail da conta (incremental).",
        "description": "Escopo `campaigns:read`, produto E-mail Marketing. Ordem `(updated_at, id)` crescente; `updated_after` (exclusivo)\nou `next_cursor` para sincronizar. `snake_case`; envelope `{data, next_cursor}`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "campaigns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Itens por página."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2048
            },
            "description": "Cursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete. Inválido → 400."
          },
          {
            "name": "updated_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339, exclusivo."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_CampaignList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/campaigns/{id}": {
      "get": {
        "operationId": "PublicGetCampaign",
        "x-asender-authz": "apikey",
        "tags": [
          "public-campaigns"
        ],
        "summary": "Uma campanha.",
        "description": "Escopo `campaigns:read`. Objeto da campanha, sem envelope. Inexistente **ou de outra conta** → **404** idêntico.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "campaigns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Id da campanha (`cmp_…`)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_Campaign"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DataNotFound"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.obter",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/campaigns/{id}/report": {
      "get": {
        "operationId": "PublicGetCampaignReport",
        "x-asender-authz": "apikey",
        "tags": [
          "public-campaigns"
        ],
        "summary": "Contadores de entrega e engajamento de uma campanha.",
        "description": "Escopo `campaigns:read`. Soma dos contadores dos e-mails da campanha: `{data: {...}}`. Inexistente **ou de outra conta** → **404** idêntico.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "campaigns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Id da campanha (`cmp_…`)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataObject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DataNotFound"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.relatorioDaCampanha",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/reports/email/overview": {
      "get": {
        "operationId": "PublicEmailReportOverview",
        "x-asender-authz": "apikey",
        "tags": [
          "public-campaigns"
        ],
        "summary": "Panorama de e-mail da conta.",
        "description": "Escopo `campaigns:read`. Totais da janela em `days` (não aceita `from`/`to`): `{data: {...}}`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "campaigns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "description": "Janela em dias terminando agora."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataObject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.relatorio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/reports/email/timeseries": {
      "get": {
        "operationId": "PublicEmailReportTimeseries",
        "x-asender-authz": "apikey",
        "tags": [
          "public-campaigns"
        ],
        "summary": "Série temporal de e-mail.",
        "description": "Escopo `campaigns:read`. Pontos por `granularity` (`hour` aceita janela curta), com os totais e o período anterior: `{data: {...}}`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "campaigns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Início: RFC 3339 ou `YYYY-MM-DD`. Use `period` OU `from`+`to`."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Fim: mesmo formato. Vai junto de `from`."
          },
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d"
              ]
            },
            "description": "Atalho: os N dias terminando hoje. Padrão `30d`."
          },
          {
            "name": "granularity",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "hour",
                "day",
                "week"
              ]
            },
            "description": "Padrão `day`."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataObject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.relatorio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/reports/email/top-emails": {
      "get": {
        "operationId": "PublicEmailReportTopEmails",
        "x-asender-authz": "apikey",
        "tags": [
          "public-campaigns"
        ],
        "summary": "E-mails com melhor (ou maior) desempenho.",
        "description": "Escopo `campaigns:read`. Ranking por `order` (`volume`, `open_rate`, `click_rate`); `min_delivered` descarta amostras pequenas: `{data: {...}}`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "campaigns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Início: RFC 3339 ou `YYYY-MM-DD`. Use `period` OU `from`+`to`."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Fim: mesmo formato. Vai junto de `from`."
          },
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d"
              ]
            },
            "description": "Atalho: os N dias terminando hoje. Padrão `30d`."
          },
          {
            "name": "order",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "volume",
                "open_rate",
                "click_rate"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Quantidade de linhas."
          },
          {
            "name": "min_delivered",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Mínimo de entregues para entrar no ranking."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataObject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.relatorio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/reports/email/domains": {
      "get": {
        "operationId": "PublicEmailReportDomains",
        "x-asender-authz": "apikey",
        "tags": [
          "public-campaigns"
        ],
        "summary": "Desempenho por domínio/provedor de destino.",
        "description": "Escopo `campaigns:read`. Entregas, aberturas e bounces por provedor: `{data: {...}}`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "campaigns:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Início: RFC 3339 ou `YYYY-MM-DD`. Use `period` OU `from`+`to`."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Fim: mesmo formato. Vai junto de `from`."
          },
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d"
              ]
            },
            "description": "Atalho: os N dias terminando hoje. Padrão `30d`."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataObject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.relatorio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/conversations": {
      "get": {
        "operationId": "PublicListConversations",
        "x-asender-authz": "apikey",
        "tags": [
          "public-conversations"
        ],
        "summary": "Conversas da conta (incremental).",
        "description": "Escopo `conversations:read`. Ordenadas por atividade ascendente; `updated_after` traz o que\nmudou — **inclusive conversa que só recebeu mensagem**. Releia com ~5 minutos de sobreposição e\ndeduplique por `id` (uma transação concorrente pode confirmar depois do carimbo já lido).\nNão saem chamados de suporte da plataforma, notas internas nem anexos (só `attachment_count`).\n`lead_id` é o id público do lead: o MESMO de `GET /v1/leads` (`id`) e de `/v1/conversions` (`lead_id`).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "conversations:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "updated_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339; estritamente depois."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Itens por página."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 300
            },
            "description": "Cursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataConversationList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "c.Listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/conversations/{id}/messages": {
      "get": {
        "operationId": "PublicListConversationMessages",
        "x-asender-authz": "apikey",
        "tags": [
          "public-conversations"
        ],
        "summary": "Mensagens de uma conversa.",
        "description": "Escopo `conversations:read`. Em ordem de chegada, com cursor. Só mensagens públicas de\ncontato, atendente, bot, aparelho e API (sem notas internas nem mensagens de sistema).\nConversa inexistente **ou de outra conta**: `404` idêntico.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "conversations:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Id da conversa (`cnv_…`)."
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339; estritamente depois."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Itens por página."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 300
            },
            "description": "Cursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataMessageList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DataNotFound"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "c.Mensagens",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/journeys": {
      "get": {
        "operationId": "PublicListJourneys",
        "x-asender-authz": "apikey",
        "tags": [
          "public-journeys"
        ],
        "summary": "Jornadas da conta (incremental).",
        "description": "Escopo `journeys:read`. Estrutura (gatilho, tipo/ação/espera de cada passo) e estatísticas\nde fluxo (`entries`, `in_progress`, `exits`). Os parâmetros dos passos (corpo de e-mail, URL de\nwebhook) **não** saem. `updated_after` filtra pela última alteração da jornada.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "journeys:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "updated_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339; estritamente depois."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Itens por página."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 300
            },
            "description": "Cursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataJourneyList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "j.Listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/journeys/{id}": {
      "get": {
        "operationId": "PublicGetJourney",
        "x-asender-authz": "apikey",
        "tags": [
          "public-journeys"
        ],
        "summary": "Uma jornada.",
        "description": "Escopo `journeys:read`. Inexistente **ou de outra conta**: `404` idêntico.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "journeys:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Id da jornada."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataJourney"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DataNotFound"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "j.Obter",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/leads": {
      "get": {
        "operationId": "PublicListLeads",
        "x-asender-authz": "apikey",
        "tags": [
          "public-data"
        ],
        "summary": "Leads da conta, em ordem de atualização (sincronização incremental).",
        "description": "Contatos do CRM da **conta do token**, do mais antigo ao mais novo por\n`(updated_at, id)`. Para sincronizar, guarde o maior `updated_at` recebido e\nenvie-o em `updated_after` (exclusivo), ou siga `next_cursor` até `null`.\nPaginação por keyset estável: linhas com o mesmo instante não se repetem nem\nse perdem. Contatos apagados ou anonimizados deixam de aparecer.\n\nEscopo `leads:read`. **Formato:** `snake_case` (diferente do `PascalCase` das\nrotas legadas de envio). Campos expostos: ver `Lead`; não saem CPF, endereço, IP,\ndono interno nem índices. Origem do CRM fora do ar: **503** com `Retry-After`.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "leads:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco (`next_cursor`). Inválido → 400."
          },
          {
            "name": "updated_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339, exclusivo. Ignorado se `cursor` vier."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_LeadList"
                }
              }
            }
          },
          "400": {
            "description": "`InvalidParameter`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "CRM indisponível (`ServiceUnavailable`, `Retry-After`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.listarLeads",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Captura de leads"
        ],
        "summary": "Enviar um lead (modo API, com API key)",
        "description": "Modo server-to-server. Exige API key com `forms:write`; a conta é resolvida da chave e o `form_id` vem no corpo. Servido no runtime, mas alcançável em `https://api.pages.asender.net` (e em `https://sites.asender.net`, igual). Sucesso é 201 (novo) ou 200 (duplicado). Reenviar com o mesmo `id_externo` é seguro (retry sem duplicar). Limite: 300 requisições por minuto por conta e 64 KB por corpo. Fora de produção use `sk_test_…` e e-mails `@exemplo.test`.",
        "security": [
          {
            "apiKey": [
              "forms:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_IdempotencyKeyLead"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_LeadAPI"
              },
              "example": {
                "form_id": "frm_exemplo",
                "campos": {
                  "email": "fulana@exemplo.test",
                  "nome": "Fulana"
                },
                "id_externo": "blog-sub-48213",
                "consentimento": true,
                "ocorrido_em": "2026-10-08T14:03:00Z",
                "origem": {
                  "url": "https://blog.exemplo.test/post/lancamento",
                  "utm_source": "newsletter",
                  "utm_medium": "email",
                  "utm_campaign": "lancamento"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "duplicada — o `id_externo` (ou `Idempotency-Key`) já foi recebido neste formulário; nada de novo gravado e o `lead_id` é o do envio original",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_LeadAceito"
                },
                "example": {
                  "ok": true,
                  "lead_id": "3f0c8f0e-6a52-4d0e-9d0b-1c6f4e2d7a10",
                  "duplicada": true,
                  "id_externo": "blog-sub-48213"
                }
              }
            }
          },
          "201": {
            "description": "lead criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_LeadAceito"
                },
                "example": {
                  "ok": true,
                  "lead_id": "3f0c8f0e-6a52-4d0e-9d0b-1c6f4e2d7a10",
                  "duplicada": false,
                  "id_externo": "blog-sub-48213"
                }
              }
            }
          },
          "400": {
            "description": "recusado: corpo inválido, campo não declarado ou inválido, consentimento exigido ausente, `ocorrido_em` fora da janela",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                },
                "example": {
                  "error": {
                    "message": "o formulário exige consentimento"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoRuntime"
          },
          "404": {
            "description": "formulário não encontrado (ou de outra conta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "429": {
            "description": "limite por conta excedido (300 por minuto por padrão). Aguarde `Retry-After` segundos.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "segundos até poder tentar de novo"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "502": {
            "description": "não consegui registrar agora; tente de novo com o mesmo `id_externo`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "503": {
            "description": "não consegui validar a chave agora; tente de novo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.API",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/leads/{id}": {
      "get": {
        "operationId": "PublicGetLead",
        "x-asender-authz": "apikey",
        "tags": [
          "public-data"
        ],
        "summary": "Um lead pelo id público.",
        "description": "Escopo `leads:read`. Id inexistente **ou de outra conta** → **404** idêntico\n(nunca 403, para não confirmar a existência).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "leads:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_Lead"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "404": {
            "description": "Lead não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "CRM indisponível.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.obterLead",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "operationId": "PublicUpdateLead",
        "x-asender-authz": "apikey",
        "tags": [
          "public-data"
        ],
        "summary": "Atualiza parcialmente um lead.",
        "description": "Altera só os campos enviados (envie ao menos um). Escopo `leads:write`, **só chave\n`sk_*`**. Texto opcional (`last_name`, `job_title`…) com `\"\"` **limpa** o valor; `tags`\npresente **substitui** a lista (vazia limpa); `custom_fields` **mescla** (valor `\"\"`\nremove a chave). Id inexistente **ou de outra conta** → **404** idêntico (nunca 403).\nMesmas regras de corpo fechado, validação e `Idempotency-Key` do `POST /v1/leads`.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/platform_LeadIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_LeadUpdate"
              },
              "example": {
                "job_title": "CEO",
                "tags": [
                  "vip",
                  "renovou"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead atualizado (ou resultado guardado, se for repetição da mesma `Idempotency-Key`).",
            "headers": {
              "Idempotent-Replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_Lead"
                }
              }
            }
          },
          "400": {
            "description": "`ValidationError` (corpo vazio, campo desconhecido ou valor inválido).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "404": {
            "description": "Lead não encontrado (inexistente ou de outra conta).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "409": {
            "description": "`IdempotencyInProgress` (`Retry-After`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "413": {
            "description": "`PayloadTooLarge`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "422": {
            "description": "`IdempotencyKeyReused` ou `Idempotency-Key` malformada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "CRM indisponível.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.atualizarLead",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/forms": {
      "get": {
        "operationId": "PublicListForms",
        "x-asender-authz": "apikey",
        "tags": [
          "public-data"
        ],
        "summary": "Formulários da conta e o schema dos seus campos.",
        "description": "Escopo `forms:read`. Ordem por `id`; `cursor` opaco. Não saem origens CORS,\ndestinos (URLs/segredos de webhook) nem o snippet.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "forms:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_FormList"
                }
              }
            }
          },
          "400": {
            "description": "`InvalidParameter`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "Pages indisponível.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.listarForms",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/forms/{id}/submissions": {
      "get": {
        "operationId": "PublicListFormSubmissions",
        "x-asender-authz": "apikey",
        "tags": [
          "public-data"
        ],
        "summary": "Envios de um formulário, do mais antigo ao mais novo.",
        "description": "Escopo `forms:read`. Ordem `(created_at, id)` crescente; use `created_after`\n(exclusivo) ou `next_cursor` para sincronizar. O corpo do formulário vem em\nclaro em `fields` (é o lead); IP, user agent, score e roteamento não saem.\nCada página grava a trilha de acesso a dado pessoal da conta. Formulário\ninexistente ou de outra conta → **404** idêntico.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "forms:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339, exclusivo. Ignorado se `cursor` vier."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_FormSubmissionList"
                }
              }
            }
          },
          "400": {
            "description": "`InvalidParameter`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "404": {
            "description": "Formulário não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "Pages ou runtime indisponível.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.listarEnvios",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/conversions": {
      "get": {
        "operationId": "PublicListConversions",
        "x-asender-authz": "apikey",
        "tags": [
          "public-data"
        ],
        "summary": "Conversões enviadas (ou não) às plataformas de anúncios, e o desfecho.",
        "description": "Escopo `conversions:read`. Fatos imutáveis do CRM — conversão `sent` à\nplataforma (Meta CAPI, Google Ads, TikTok), `ignored` (com o motivo, ex.:\nsem consentimento) e `result` da entrega — em ordem de registro. Use\n`created_after` (exclusivo) ou `next_cursor`. Sem payload cru, token ou hash;\n`lead_id` é o id público do lead.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "conversions:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ConversionList"
                }
              }
            }
          },
          "400": {
            "description": "`InvalidParameter`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "CRM indisponível.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataError"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.listarConversoes",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/messaging/sms/messages": {
      "get": {
        "operationId": "PublicListSmsMessages",
        "x-asender-authz": "apikey",
        "tags": [
          "public-messaging"
        ],
        "summary": "Mensagens de SMS enviadas pela conta (incremental).",
        "description": "Escopo `messaging:read`, produto `sns`. Em ordem de criação **crescente**: use `created_after` (exclusivo) ou `next_cursor` para sincronizar.\nCampos: `id`, `to`, `status`, `queued_at`, `created_at`, `segments`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "messaging:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Itens por página."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2048
            },
            "description": "Cursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete. Inválido → 400."
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339, exclusivo. Sem ele a lista começa na mensagem mais antiga."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_MessagingMessageList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.mensagens",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/messaging/sms/reports/overview": {
      "get": {
        "operationId": "PublicSmsReportOverview",
        "x-asender-authz": "apikey",
        "tags": [
          "public-messaging"
        ],
        "summary": "Panorama de SMS da conta.",
        "description": "Escopo `messaging:read`, produto `sns`. Totais por status na janela `days`: `{data: {...}}`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "messaging:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "description": "Janela em dias terminando agora."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataObject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.panorama",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/messaging/whatsapp/messages": {
      "get": {
        "operationId": "PublicListWhatsappMessages",
        "x-asender-authz": "apikey",
        "tags": [
          "public-messaging"
        ],
        "summary": "Mensagens de WhatsApp enviadas pela conta (incremental).",
        "description": "Escopo `messaging:read`, produto `zapify`. Em ordem de criação **crescente**: use `created_after` (exclusivo) ou `next_cursor` para sincronizar.\nCampos: `id`, `to`, `status`, `queued_at`, `created_at`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "messaging:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Itens por página."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2048
            },
            "description": "Cursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete. Inválido → 400."
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339, exclusivo. Sem ele a lista começa na mensagem mais antiga."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_MessagingMessageList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.mensagens",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/messaging/whatsapp/reports/overview": {
      "get": {
        "operationId": "PublicWhatsappReportOverview",
        "x-asender-authz": "apikey",
        "tags": [
          "public-messaging"
        ],
        "summary": "Panorama de WhatsApp da conta.",
        "description": "Escopo `messaging:read`, produto `zapify`. Totais por status na janela `days`: `{data: {...}}`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "messaging:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "description": "Janela em dias terminando agora."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataObject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.panorama",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/messaging/push/messages": {
      "get": {
        "operationId": "PublicListPushMessages",
        "x-asender-authz": "apikey",
        "tags": [
          "public-messaging"
        ],
        "summary": "Mensagens de push enviadas pela conta (incremental).",
        "description": "Escopo `messaging:read`, produto `push`. Em ordem de criação **crescente**: use `created_after` (exclusivo) ou `next_cursor` para sincronizar.\nCampos: `id`, `to`, `status`, `queued_at`, `created_at`, `title`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "messaging:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Itens por página."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2048
            },
            "description": "Cursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete. Inválido → 400."
          },
          {
            "name": "created_after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339, exclusivo. Sem ele a lista começa na mensagem mais antiga."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_MessagingMessageList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.mensagens",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/messaging/push/reports/overview": {
      "get": {
        "operationId": "PublicPushReportOverview",
        "x-asender-authz": "apikey",
        "tags": [
          "public-messaging"
        ],
        "summary": "Panorama de push da conta.",
        "description": "Escopo `messaging:read`, produto `push`. Totais por status na janela `days`: `{data: {...}}`.\nConta cujo canal ainda não migrou para a ferramenta dedicada → **503** (`ServiceUnavailable`, `Retry-After`).\nParâmetro de query fora da lista → **400** (nada é ignorado em silêncio).",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": [
              "messaging:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "description": "Janela em dias terminando agora."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DataObject"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_DataBadRequest"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DataForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_DataBadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DataUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.panorama",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "304 quando o `If-None-Match` bate; senão o bundle, com cache de uma hora e `X-Content-Type-Options: nosniff`.",
        "description": "304 quando o `If-None-Match` bate; senão o bundle, com cache de uma hora e `X-Content-Type-Options: nosniff`.\n\nOnde é usada: rota `/chat.js` do runtime.\n\nEntradas: w; r.\n\nEfeitos: nenhum — é leitura de bytes já em memória. **Não há CORS aqui, e é de propósito.** Um `<script src>` não precisa de CORS para executar, e liberar `Access-Control-Allow-Origin: *` neste arquivo só abriria a leitura do conteúdo por `fetch` de qualquer origem sem ganho nenhum. Quem precisa de CORS é a API do chat, e lá a origem é conferida contra a chave de site — não liberada em `*`. `ifNoneMatchCasa` (comparação FRACA, RFC 9110 §13.1.2), não `==`: a borda comprime este script on-the-fly e rebaixa o `ETag` forte para `W/\"…\"` na resposta — o navegador devolve esse `W/` na revisita, e `==` contra o forte nunca batia (achado em `embed.js`, forms-rapidos 2026-10-01; mesma causa aqui, mesmo remédio — ver `etag_match.go`).",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pagina.Servir",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "resolve a página pela mesma rota que a serviu, monta a submissão com `Modo = pagina` e chama o MESMO pipeline dos outros dois modos.",
        "description": "resolve a página pela mesma rota que a serviu, monta a submissão com `Modo = pagina` e chama o MESMO pipeline dos outros dois modos.\n\nOnde é usada: POST no caminho da própria página.\n\nEfeitos: os do pipeline (banco, outbox). O tenant e o form vêm da PÁGINA resolvida no servidor, nunca do corpo: o form da página é HTML público, e qualquer um pode reenviá-lo com outro `form_id`. Resolver pelo host+caminho é o que impede postar no formulário de outro tenant.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pagina.Submeter",
        "x-asender-fonte": "codigo"
      }
    },
    "/version": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "nome do serviço, versão e o commit que gerou o binário.",
        "description": "nome do serviço, versão e o commit que gerou o binário.\n\nOnde é usada: rota pública, usada por ops e por quem investiga incidente.\n\nEfeitos: escreve JSON na resposta. # Por que ela é PÚBLICA, e o que ela não conta A primeira pergunta de todo incidente é \"que versão está no ar?\" — e ela tem de ser respondível sem credencial, porque quem investiga muitas vezes ainda não tem uma. O que sai é o que já está no `docker inspect` de quem tem acesso ao host: nome, versão e commit. Nunca configuração, nunca dependência, nunca endereço interno — isso é topologia, e topologia é do console de plataforma. A versão vem de LDFLAGS no build, e o default é `dev`: um binário sem carimbo diz `dev` em vez de mentir uma versão que ninguém emitiu.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "h.Versao",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/push/site/devices": {
      "post": {
        "operationId": "PublicRegisterSiteDevice",
        "x-asender-authz": "public",
        "tags": [
          "public-push"
        ],
        "summary": "Registra a inscrição de Web Push do visitante, a partir do navegador.",
        "description": "Rota do snippet de push colado no site do cliente (M15, ADR-0057).\nAutenticada pela chave PÚBLICA de site (`?site_key=pk_push_…`, emitida\npor `GET /api/push/site-config`), nunca por API key de servidor — que é\nrecusada aqui (401), assim como a chave de site é recusada em toda outra\nrota. Exige `Origin` declarado na chave (403 `OriginNotAllowed`, sem\nCORS). O `OPTIONS` (preflight) faz a mesma checagem de chave e origem e\nresponde 204 com CORS da origem aprovada. Teto por (chave, IP) e por\nchave (`PUSH_SITE_RATE_LIMIT_*`). O tenant vem da chave assinada e é\nconferido `active` no core a cada registro. Upsert por endpoint no\n`asender_mkt_api`; a resposta não devolve o registro interno.",
        "security": [
          {
            "SiteKeyQuery": []
          }
        ],
        "parameters": [
          {
            "name": "site_key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^pk_push_",
              "maxLength": 4096
            }
          },
          {
            "name": "Origin",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "Endpoint",
                  "P256dh",
                  "Auth"
                ],
                "properties": {
                  "Platform": {
                    "type": "string",
                    "enum": [
                      "web"
                    ],
                    "description": "Opcional; só `web`."
                  },
                  "Endpoint": {
                    "type": "string",
                    "format": "uri",
                    "description": "`PushSubscription.endpoint` (https)."
                  },
                  "P256dh": {
                    "type": "string"
                  },
                  "Auth": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Inscrição registrada ou reativada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "Status"
                  ],
                  "properties": {
                    "Status": {
                      "type": "string",
                      "enum": [
                        "registered"
                      ]
                    },
                    "RequestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`OriginNotAllowed` (origem ausente/não declarada) ou `TenantSuspended`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "Segredo de verificação da chave de site ausente."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "sitePushH.Register",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/emails/send": {
      "post": {
        "operationId": "PublicSendEmail",
        "x-asender-authz": "apikey",
        "tags": [
          "public-email"
        ],
        "summary": "Enfileira um email (payload compatível com SES SendEmail).",
        "description": "Persiste no `asender_mkt_api` (mensagem + outbox no MESMO commit) e o\npublisher de outbox entrega ao NATS. **Não publica direto no broker** —\nseria dual-write.\n\n**Serviço de mensageria indisponível → `202 accepted` (não 502).** O\npedido é persistido em disco pela API e reenviado em ordem com a mesma\n`Idempotency-Key` (a sua, ou uma gerada antes da 1ª tentativa); veja\n`Idempotency-Key` em Parâmetros. `4xx` de validação seguem síncronos.\n\nCc e Bcc entram como destinatários independentes: o serviço materializa\n**uma mensagem por destinatário**, que é o comportamento correto de\ncópia oculta.\n\n**`Source` só de domínio verificado (quebra de contrato, 2026-10-02).**\nO `Source` tem de ser de domínio VERIFICADO nesta conta (SPF e DKIM, em\nAdmin → Domínios). Senão: **422 `RemetenteNaoVerificado`** (inclusive\ndomínio verificado em OUTRA conta), com `Message` que nomeia o domínio e\ntraz o link de Domínios; verificação fora → **503\n`VerificacaoIndisponivel`** com `Retry-After`. Nada é enfileirado nem\ncobrado. Antes desta data o pedido era aceito (202) e o e-mail saía com\no remetente da plataforma e o `Source` em `Reply-To`. Se o domínio perde\na verificação DEPOIS do 202, a mensagem fica `failed`\n(`RemetenteNaoVerificado`) — nunca sai com outro remetente.\n\n**Destinatário suprimido é explícito (ADR-0134, 2026-10-21).** O Asender\nnão envia a destinatário na supressão global (TLD fora da lista da IANA,\ndomínio de teste, caixa inexistente, duas recusas em 30 dias, entrada\nmanual…). Lote com destinatários enviáveis E suprimidos: **202** com\n`Suppressed[]` (`Email`, `Reason`, `Message`) — só os enviáveis ganham\nmensagem em `MessageIds`. Todos suprimidos: **422 `SuppressedRecipient`**\ncom a `Message` humana. Depois, `GET /v1/emails/{id}` de uma mensagem\nsuprimida traz `Status: suppressed`, `SuppressionReason` e `ErrorMessage`\nhumano; suprimido **não** é falha. Motivos em `SuppressionReason`.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_SendEmailRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/platform_EnqueuedEmail"
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "409": {
            "$ref": "#/components/responses/platform_IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/platform_RemetenteOuSuprimido"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_VerificacaoIndisponivel"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailH.Send",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/emails/batch": {
      "post": {
        "operationId": "PublicBatchEmail",
        "x-asender-authz": "apikey",
        "tags": [
          "public-email"
        ],
        "summary": "Até 500 emails independentes numa chamada.",
        "description": "**Resultado parcial é legítimo e explícito.** Falha de um item não\nderruba os outros: cada entrada de `Results` traz `MessageId` ou\n`Error`, sempre com o `Index` de origem.\n\n`Idempotency-Key` vale por item (`<chave>:<índice>`); cada item traz\n`ReusedIdempotency`, e um item que reusa a chave com outro corpo falha com\n`Error.Code` `IdempotencyConflict`.\n\nItem com `Source` de domínio não verificado (2026-10-02): `Status:\nfailed` com `Error.Code` `RemetenteNaoVerificado` e a `Message` que diz\no domínio e o link de Domínios (ou `VerificacaoIndisponivel` com a\nverificação fora). Outras recusas do upstream continuam genéricas\n(`ValidationError`/`InternalError`, sem o motivo interno).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "Messages"
                ],
                "properties": {
                  "Messages": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "$ref": "#/components/schemas/platform_SendEmailRequest"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Lote processado (com ou sem itens rejeitados).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Count": {
                          "type": "integer"
                        },
                        "Results": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_BatchResult"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailH.Batch",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/emails": {
      "get": {
        "operationId": "PublicListEmails",
        "x-asender-authz": "apikey",
        "tags": [
          "public-email"
        ],
        "summary": "Histórico de emails do tenant da API key.",
        "description": "`channel` é FIXO em `email` nesta rota — mandar `?channel=sms` é **422**\n(parâmetro fora da allowlist), não filtro silencioso.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/platform_MessageStatus"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Busca. `%` e `_` são escapados no repositório.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "description": "RFC3339, estrito (>). Sincronização incremental: a lista passa a sair em ordem **ascendente** e `cursor` anda para frente. Inválido → 422; conta cujo canal ainda não migrou → 501.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Página de mensagens.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_MessagePage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailH.List",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Envio"
        ],
        "summary": "Enviar e-mail a partir de uma caixa da conta (público)",
        "description": "Exige o escopo `email:send` (sem ele, 403 `InsufficientScope`). Envia de uma CAIXA que existe na conta da chave (o domínio verificado não basta: crie a caixa do `from` antes, pelo painel), assinado com o DKIM do domínio. Para remetente de qualquer endereço de um domínio verificado (incluindo OTP/2FA), use `POST https://api.asender.net/v1/emails/send`. Idempotente por `Idempotency-Key` (a mesma chave + tenant não dispara dois e-mails; o replay volta 200 com `idempotent_replay:true`). Consome a quota de disparos do plano — um por destinatário em `to`/`cc`/`bcc` — e responde 429 quando o teto (diário ou mensal) é atingido. Fora de produção, o piso 18 bloqueia destinos não-allowlistados (o e-mail não sai).",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "repita a mesma chave para tornar seguro o retry de uma resposta perdida."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Email"
              },
              "examples": {
                "transacional": {
                  "summary": "E-mail enviado de uma caixa",
                  "value": {
                    "from": "joana@empresa.com.br",
                    "to": [
                      "cliente@exemplo.test"
                    ],
                    "subject": "Proposta comercial",
                    "text": "Olá, segue a proposta que conversamos."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "replay idempotente (já enviado antes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_EmailAceito"
                }
              }
            }
          },
          "202": {
            "description": "aceito e entregue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_EmailAceito"
                }
              }
            }
          },
          "400": {
            "description": "validação (from inválido, to vazio, header-injection em endereço, anexo inválido).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/mail_NaoAutorizado"
          },
          "403": {
            "description": "`InsufficientScope` — a chave não tem o escopo `email:send` (sem escopos, `full_access`, `send_only` e `emails:*` também caem aqui).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "422": {
            "description": "o antivírus encontrou malware num anexo (`AnexoInfectado`); nada foi enviado (ADR-0087).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "429": {
            "description": "Limite de disparos do plano atingido no período. Os cabeçalhos dizem qual janela barrou e quando reabre.",
            "headers": {
              "X-Quota-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "teto do plano no período que barrou"
              },
              "X-Quota-Used": {
                "schema": {
                  "type": "integer"
                },
                "description": "disparos já consumidos"
              },
              "X-Quota-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "disparos restantes"
              },
              "X-Quota-Escala": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "dia",
                    "mes"
                  ]
                },
                "description": "qual janela barrou"
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "segundos até a janela reabrir"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "502": {
            "description": "envio recusado (piso 18 fora de prd) ou a entrega falhou; a quota é estornada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "503": {
            "description": "envio não configurado no serviço (boot sem outbound), ou mensagem COM anexo com o antivírus configurado e fora (`AntivirusIndisponivel`, com Retry-After) — nada sai sem exame.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.EnviarPublico",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/emails/{id}": {
      "get": {
        "operationId": "PublicGetEmail",
        "x-asender-authz": "apikey",
        "tags": [
          "public-email"
        ],
        "summary": "Um email com corpo, metadata e timeline de eventos.",
        "description": "Mensagem de outro tenant → **404** (não confirma existência). Mensagem\ndeste tenant em outro canal também → 404: nesta rota só email existe.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_MessageId"
          }
        ],
        "responses": {
          "200": {
            "description": "Detalhe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_MessageDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailH.Get",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/sms/send": {
      "post": {
        "operationId": "PublicSendSMS",
        "x-asender-authz": "apikey",
        "tags": [
          "public-sms"
        ],
        "summary": "Enfileira um SMS.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "To",
                  "Body"
                ],
                "properties": {
                  "From": {
                    "type": "string",
                    "description": "E.164, opcional."
                  },
                  "To": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "description": "E.164"
                    }
                  },
                  "Body": {
                    "type": "string",
                    "maxLength": 1600
                  },
                  "IdempotencyKey": {
                    "type": "string"
                  },
                  "Tags": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/platform_Enqueued"
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "409": {
            "$ref": "#/components/responses/platform_IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "valida o payload SES-shaped e persiste o envio no asender_mkt_api, que enfileira para o worker de email.\n\nOnde é usada: API pública do cliente (autenticada por API key).\n\nSaídas: 202 com `{MessageId, MessageIds, Status, ReusedIdempotency, Suppressed?}`; 400 em validação local; 422 quando o asender_mkt_api recusa — com o código `RemetenteNaoVerificado` quando o Source não é de domínio verificado da conta (2026-10-02: antes o e-mail saía com o remetente da plataforma); 503 `VerificacaoIndisponivel` quando a verificação está fora; 502 se ele está fora.\n\nEfeitos: escrita no serviço de mensageria. Idempotente por IdempotencyKey.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "smsH.Send",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/whatsapp/send": {
      "post": {
        "operationId": "PublicSendWhatsApp",
        "x-asender-authz": "apikey",
        "tags": [
          "public-whatsapp"
        ],
        "summary": "Enfileira uma mensagem de WhatsApp (texto livre ou template).",
        "description": "Mesmo padrão do SMS: `202` quer dizer **enfileirado**, não entregue. O\nopt-out do canal (destinatário que saiu do WhatsApp), a quota do plano e\no limite de vazão são aplicados no enfileiramento; quem saiu do canal\nfica de fora e entra na contagem `Suppressed` (se ninguém sobra, `422`).\n\n**Janela de 24h da Meta.** `Body` (texto livre) só é entregue se o\ndestinatário escreveu para o seu número nas últimas 24h. Fora da janela\na Meta recusa e a mensagem termina `failed` (`OutsideWindow`) — isso é\nassíncrono, o `202` já foi dado. Para **abrir** conversa use `Template`:\no slug de um template **aprovado na Meta e registrado na conta**\n(Admin → WhatsApp → Templates), com `Variables` posicionais `\"1\"..\"N\"` —\nexatamente o número que o template exige, senão `422`. Mande `Body` **ou**\n`Template`, nunca os dois.\n\n`From` é o `phone_number_id` numérico do número da conta; omitido, vale o\nnúmero padrão. Sem credencial do WhatsApp configurada na conta, o envio\nnão sai (a mensagem fica `failed`).\n\nChave de servidor **nunca** vai para o navegador: pedido com `Origin` /\n`Sec-Fetch-*` leva `403 ServerKeyInBrowser`.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "To"
                ],
                "properties": {
                  "From": {
                    "type": "string",
                    "pattern": "^[0-9]{1,64}$",
                    "description": "phone_number_id numérico (opcional)."
                  },
                  "To": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "type": "string",
                      "description": "E.164, ex. +5511999990000"
                    }
                  },
                  "Body": {
                    "type": "string",
                    "maxLength": 4096,
                    "description": "Texto livre (janela de 24h). Exclusivo com Template."
                  },
                  "Template": {
                    "type": "string",
                    "description": "Slug do template registrado na conta. Exclusivo com Body."
                  },
                  "Variables": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Variáveis posicionais do template: \"1\", \"2\", …"
                  },
                  "IdempotencyKey": {
                    "type": "string",
                    "description": "O header Idempotency-Key tem precedência."
                  },
                  "Tags": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              },
              "examples": {
                "textoLivre": {
                  "summary": "Texto livre (dentro da janela de 24h)",
                  "value": {
                    "To": [
                      "+5511999990000"
                    ],
                    "Body": "Olá! Seu pedido saiu para entrega.",
                    "IdempotencyKey": "pedido-1042-aviso"
                  }
                },
                "template": {
                  "summary": "Template aprovado (abre conversa fora da janela)",
                  "value": {
                    "To": [
                      "+5511999990000"
                    ],
                    "Template": "promo_outubro",
                    "Variables": {
                      "1": "Ana",
                      "2": "10%"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Aceito e enfileirado; a entrega é assíncrona.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "MessageId": {
                          "type": "string"
                        },
                        "MessageIds": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Um id por destinatário aceito."
                        },
                        "Status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "accepted"
                          ],
                          "description": "`accepted`: o destino estava indisponível; pedido no spool durável, reenviado com a mesma `Idempotency-Key`."
                        },
                        "IdempotencyKey": {
                          "type": "string",
                          "description": "Só com `Status: accepted`: a chave efetiva do replay (também no header `Idempotency-Key`)."
                        },
                        "ReusedIdempotency": {
                          "type": "boolean",
                          "description": "`true` no replay da mesma `Idempotency-Key` com o MESMO corpo."
                        },
                        "Suppressed": {
                          "type": "integer",
                          "description": "Destinatários fora do canal (opt-out) que não foram enfileirados."
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "MessageId": "msg_01k8x2m9c4",
                  "MessageIds": [
                    "msg_01k8x2m9c4"
                  ],
                  "Status": "queued",
                  "ReusedIdempotency": false,
                  "Suppressed": 0,
                  "RequestId": "req_5d1c"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`TenantSuspended` (conta não ativa) ou `ServerKeyInBrowser` (chave de servidor usada de um navegador).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/platform_IdempotencyConflict"
          },
          "422": {
            "description": "`ValidationError`: template inexistente na conta, variáveis em número errado, ou todos os destinatários fora do canal (opt-out). Nada é enfileirado nem cobrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappSendH.Send",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/push/send": {
      "post": {
        "operationId": "PublicSendPush",
        "x-asender-authz": "apikey",
        "tags": [
          "public-push"
        ],
        "summary": "Enfileira um push para aparelhos registrados (DeviceTokens).",
        "description": "O destino é sempre uma lista de `DeviceTokens` de aparelhos registrados\nem `POST /v1/push/devices` (pelo menos um). **Envio por tópico não é\nsuportado:** o campo `Topic` é recusado com `400 ValidationError`\n(`Error.Details.Topic`), junto ou sem `DeviceTokens`. Sem tokens, `400`\ncom `Details.DeviceTokens`.\n\n`202` quer dizer enfileirado, não entregue. Se nenhum dos tokens é um\naparelho ativo da conta (desregistrado ou descadastrado), a resposta é\n`422 ValidationError` e nada é cobrado.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "DeviceTokens",
                  "Notification"
                ],
                "properties": {
                  "DeviceTokens": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string"
                    }
                  },
                  "Topic": {
                    "type": "string",
                    "deprecated": true,
                    "description": "NÃO suportado: enviar este campo resulta em `400 ValidationError` (`Details.Topic`). Use `DeviceTokens`."
                  },
                  "Notification": {
                    "type": "object",
                    "required": [
                      "Title",
                      "Body"
                    ],
                    "properties": {
                      "Title": {
                        "type": "string"
                      },
                      "Body": {
                        "type": "string"
                      }
                    }
                  },
                  "Data": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "IdempotencyKey": {
                    "type": "string"
                  },
                  "Tags": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/platform_Enqueued"
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "409": {
            "$ref": "#/components/responses/platform_IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "pushH.Send",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/push/devices": {
      "post": {
        "operationId": "PublicRegisterDevice",
        "x-asender-authz": "apikey",
        "tags": [
          "public-push"
        ],
        "summary": "Registra (upsert por token) um device de push.",
        "description": "Idempotente pelo par (tenant, token). Responde **200** sempre, mesmo\nquando o `asender_mkt_api` respondeu 201 no primeiro registro.\n\n**Só servidor-a-servidor.** Pedido com `Origin` ou `Sec-Fetch-*` (sinal\nde navegador) é recusado com 403 `ServerKeyInBrowser` e logado com o id\nda chave: a API key de servidor não pode estar num site. Do navegador,\nuse `POST /v1/push/site/devices` com a chave de site (M15, ADR-0057).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "Token",
                  "Platform"
                ],
                "properties": {
                  "Token": {
                    "type": "string"
                  },
                  "Platform": {
                    "type": "string",
                    "enum": [
                      "ios",
                      "android",
                      "web"
                    ]
                  },
                  "UserRef": {
                    "type": "string",
                    "description": "ACEITO E NÃO PERSISTIDO — sem campo no contrato interno; a perda é logada (Warn), não silenciosa."
                  },
                  "Metadata": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "ACEITO E NÃO PERSISTIDO — idem UserRef."
                  }
                }
              }
            }
          }
        },
        "x-asender-divergence": "`UserRef` e `Metadata` são aceitos e descartados (logados em Warn). O\ncontrato de devices do asender_mkt_api não tem esses campos.\n",
        "responses": {
          "200": {
            "description": "Device registrado ou reativado.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Device": {
                          "$ref": "#/components/schemas/platform_Device"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`TenantSuspended` (tenant não-`active`) ou `ServerKeyInBrowser`\n(API key de servidor usada a partir de navegador).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "pushH.RegisterDevice",
        "x-asender-fonte": "spec+codigo"
      },
      "get": {
        "operationId": "PublicListDevices",
        "x-asender-authz": "apikey",
        "tags": [
          "public-push"
        ],
        "summary": "Lista os devices de push da conta da API key.",
        "description": "Contraparte de leitura do registro: quem envia token precisa poder\nconferir o que está registrado. O tenant vem da API key, NUNCA da query —\naceitar `tenant_id` do cliente aqui seria vazamento cross-tenant.",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Página de devices, em PascalCase.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "Devices",
                    "NextCursor",
                    "HasMore"
                  ],
                  "properties": {
                    "Devices": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "NextCursor": {
                      "type": "integer"
                    },
                    "HasMore": {
                      "type": "boolean"
                    },
                    "RequestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "pushH.ListDevices",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/senders": {
      "get": {
        "operationId": "PublicListSenders",
        "x-asender-authz": "apikey",
        "tags": [
          "public-email"
        ],
        "summary": "Remetentes que a chave pode usar como `from` (domínios e caixas da conta).",
        "description": "Descubra os remetentes válidos **antes** de enviar. O tenant é o da API\nkey; não há parâmetro de conta (query ou header do cliente é ignorado).\nFonte: o `asender_mail_api`, que é quem verifica domínios e guarda as\ncaixas.\n\n- `Domains` — **todos** os domínios da conta. `Verified` = ativo com SPF\n e DKIM ok; só o verificado traz `UsableFor: [\"emails/send\"]`. Domínio\n não verificado **é listado** com `Verified: false` e `UsableFor: []`,\n para você ver o que falta. `DnsStatus` vem da verificação automática\n de DNS: `ok`, `pending` (ainda esperando o DNS propagar), `outdated`\n (um registro mudou depois de verificado) ou `unknown` (sem leitura).\n- `Mailboxes` — caixas **ativas**. Em domínio verificado: `UsableFor`\n `[\"emails/send\", \"mail:/v1/emails\"]`; em domínio não verificado só\n `[\"mail:/v1/emails\"]`. Em `POST /v1/emails/send` basta o domínio estar\n verificado; a caixa só é exigida na Mail API (`POST /v1/emails`).\n\nEscopo: `email:send`. Mail API fora do ar: **503 `SendersUnavailable`**\ncom `Retry-After` (nunca uma lista parcial).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Domains": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "Name": {
                                "type": "string",
                                "examples": [
                                  "empresa.com.br"
                                ]
                              },
                              "Verified": {
                                "type": "boolean"
                              },
                              "UsableFor": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "enum": [
                                    "emails/send"
                                  ]
                                }
                              },
                              "DnsStatus": {
                                "type": "string",
                                "enum": [
                                  "ok",
                                  "pending",
                                  "outdated",
                                  "unknown"
                                ]
                              }
                            }
                          }
                        },
                        "Mailboxes": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "Address": {
                                "type": "string",
                                "examples": [
                                  "no-reply@empresa.com.br"
                                ]
                              },
                              "UsableFor": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "enum": [
                                    "emails/send",
                                    "mail:/v1/emails"
                                  ]
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "Domains": [
                    {
                      "Name": "empresa.com.br",
                      "Verified": true,
                      "UsableFor": [
                        "emails/send"
                      ],
                      "DnsStatus": "ok"
                    },
                    {
                      "Name": "novo.com.br",
                      "Verified": false,
                      "UsableFor": [],
                      "DnsStatus": "pending"
                    }
                  ],
                  "Mailboxes": [
                    {
                      "Address": "no-reply@empresa.com.br",
                      "UsableFor": [
                        "emails/send",
                        "mail:/v1/emails"
                      ]
                    },
                    {
                      "Address": "oi@novo.com.br",
                      "UsableFor": [
                        "mail:/v1/emails"
                      ]
                    }
                  ],
                  "RequestId": "req_abc123"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "`503 SendersUnavailable` — o diretório de remetentes (Mail API) não\nrespondeu. Repita depois do `Retry-After`.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Segundos até tentar de novo."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "sendersH.List",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "PublicListWebhooks",
        "x-asender-authz": "apikey",
        "tags": [
          "public-webhooks"
        ],
        "summary": "Lista os endpoints de webhook da conta da API key.",
        "description": "Escopo `webhooks:manage` (**só chave**; token OAuth recebe 403). O tenant vem da\nchave, nunca da query. O segredo de assinatura **não** é devolvido aqui.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Endpoints da conta, em PascalCase.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "Webhooks"
                      ],
                      "properties": {
                        "Webhooks": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_Webhook"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "webhooksPubH.List",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "PublicCreateWebhook",
        "x-asender-authz": "apikey",
        "tags": [
          "public-webhooks"
        ],
        "summary": "Cadastra um endpoint de webhook.",
        "description": "Escopo `webhooks:manage` (só chave). A resposta traz o `Secret` (`whsec_…`) **uma\núnica vez** (`Cache-Control: no-store`): guarde-o agora, ele nunca mais é exibido —\nperdeu, apague o endpoint e crie outro. O segredo é sempre gerado pelo Asender (não\naceita o seu). Com ele você confere a assinatura das entregas (formato\n**Asender Webhook v1**, ADR-0133: `Asender-Signature: t=<unix>,v1=<hex>`, HMAC-SHA256 do\nsegredo sobre `\"<t>.<corpo cru>\"`).\n\n**Validação.** `Url` absoluta, `https` em produção (`http` só fora de produção), até 2048\ncaracteres, sem credencial nem fragmento e **sem destino interno** (loopback, faixa\nprivada, link-local/metadados de nuvem, `localhost`, `.local`, `.internal`) — o destino\né conferido de novo, no endereço resolvido, a cada entrega (anti-SSRF).\n`Events` é uma lista de 1 a 32 itens **sem repetição**, cada um `*` (tudo), `<família>.*`\n(`email`, `sms`, `whatsapp`, `push`, `mail`) ou um tipo exato do catálogo entregável:\n`email.queued`, `email.failed`, `sms.sent`, `sms.failed`, `whatsapp.sent`,\n`whatsapp.failed`, `push.delivered`, `push.failed`, `mail.sent`, `mail.received`.\nEventos internos (`interno.*`) nunca são entregues.\n\n**Sem `Idempotency-Key`.** Esta rota não guarda estado de idempotência: repetir o `POST`\ncria outro endpoint. Em caso de dúvida, liste antes (`GET /v1/webhooks`).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_WebhookCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Endpoint criado; o `Secret` só aparece nesta resposta.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_WebhookCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "webhooksPubH.Create",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "operationId": "PublicGetWebhook",
        "x-asender-authz": "apikey",
        "tags": [
          "public-webhooks"
        ],
        "summary": "Um endpoint de webhook da conta.",
        "description": "Escopo `webhooks:manage` (só chave). Inexistente **ou de outra conta**: `404` idêntico. Sem o segredo.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "O endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "$ref": "#/components/schemas/platform_Webhook"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "webhooksPubH.Get",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "operationId": "PublicUpdateWebhook",
        "x-asender-authz": "apikey",
        "tags": [
          "public-webhooks"
        ],
        "summary": "Altera URL, eventos e/ou estado de um endpoint.",
        "description": "Escopo `webhooks:manage` (só chave). Envie ao menos um campo; as mesmas regras do `POST`\nvalem para o que for enviado. Não rotaciona o segredo. `Active: false` pausa as entregas.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "O endpoint atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "$ref": "#/components/schemas/platform_Webhook"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "webhooksPubH.Patch",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "PublicDeleteWebhook",
        "x-asender-authz": "apikey",
        "tags": [
          "public-webhooks"
        ],
        "summary": "Apaga um endpoint de webhook.",
        "description": "Escopo `webhooks:manage` (só chave). Apagamento lógico; as entregas pendentes para ele deixam de sair. Inexistente ou de outra conta: `404`.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "responses": {
          "204": {
            "description": "Apagado. Sem corpo."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "webhooksPubH.Delete",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "operationId": "PublicTestWebhook",
        "x-asender-authz": "apikey",
        "tags": [
          "public-webhooks"
        ],
        "summary": "Envia um evento `webhook.test` assinado a UM endpoint.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^whk_[A-Za-z0-9_-]+$"
            },
            "description": "Id público do endpoint (`whk_…`)."
          }
        ],
        "description": "Escopo `webhooks:manage` (só chave). Faz **um** `POST` síncrono, sem retentativa, só para\no endpoint pedido (o evento `webhook.test` não é assinável e não vai para os outros\nendpoints da conta). Leva os cabeçalhos atuais (`X-Asender-*`) e os do formato v1\n(`Asender-Webhook-Id`, `Asender-Event`, `Asender-Signature`). O corpo da resposta do seu\nservidor **não** é devolvido.\n\nResponde **200** sempre que o endpoint existe; o resultado da entrega está no corpo:\n`Delivered`, `StatusCode` (0 se não houve resposta) e `Reason` — `\"\"` (entregue),\n`non_2xx`, `transport_error`, `destination_blocked` (destino interno) ou\n`non_production_blocked` (fora de produção só domínios de teste, p.ex.\n`*.test.asender.com.br`/`.test`/`.example`; nada chega a terceiros).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Resultado do envio de teste.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_WebhookTestResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "webhooksPubH.Test",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dns/zones": {
      "get": {
        "operationId": "PublicListDnsZones",
        "x-asender-authz": "apikey",
        "tags": [
          "public-dns"
        ],
        "summary": "Lista as zonas de DNS da conta.",
        "description": "Escopo `dns:read` (**só chave**; token OAuth recebe 403). Devolve as zonas de todas as\nconexões Cloudflare da conta (a conexão — o token da Cloudflare — é colada no painel; **não\nhá rota de conexão nesta API**). Se a Cloudflare falhar para uma conexão, o cache aparece e a\nconexão vem em `warnings`. Sem parâmetros de consulta (parâmetro desconhecido é `400`).\nResposta `Cache-Control: no-store`.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Zonas e avisos, em inglês e snake_case.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsZones"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DnsForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DnsNotFound"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DnsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "dnsPubH.ListZones",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dns/zones/{zona}/records": {
      "get": {
        "operationId": "PublicListDnsRecords",
        "x-asender-authz": "apikey",
        "tags": [
          "public-dns"
        ],
        "summary": "Lista os registros de uma zona.",
        "description": "Escopo `dns:read` (só chave). A Cloudflare é lida por inteiro; `page`/`per_page` recortam o\nresultado. Zona inexistente **ou de outra conta**: `404` idêntico. Parâmetro de consulta\ndesconhecido (inclusive `tenant_id`) é `400`: filtro digitado errado nunca vira \"sem filtro\".",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "TXT"
              ]
            },
            "description": "Tipo do registro: A, AAAA, CNAME, TXT, MX, NS…"
          },
          {
            "name": "name",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Nome completo (FQDN) ou relativo à zona."
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Registros e total.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsRecords"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DnsForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DnsNotFound"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DnsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "dnsPubH.ListRecords",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "PublicCreateDnsRecord",
        "x-asender-authz": "apikey",
        "tags": [
          "public-dns"
        ],
        "summary": "Cria um registro de DNS na zona.",
        "description": "Escopo `dns:write` (só chave). Tipos aceitos: `A`, `AAAA`, `CNAME`, `TXT`, `MX`, `NS`.\n`ttl` é `1` (automático) ou de 60 a 86400 (padrão `1`); `priority` é obrigatório só no `MX`.\n`NS` no apex é recusado (`422 ProtectedRecord`).\n\n**Controles de escrita.**\n- `dry_run=1` (ou `true`) devolve o **plano** (`dry_run: true`, `action`, `before`, `after`,\n `requires_confirmation`, `confirm`) sem tocar a Cloudflare; fica no histórico como `dry_run`.\n Qualquer outro valor que não seja `1`/`true`/`0`/`false` é `400`, para que `dry_run=yes`\n nunca execute a escrita de verdade.\n- `X-Asender-Confirmar: <nome do registro>` é **obrigatório** para `MX`, SPF e DMARC do apex\n (`428 ConfirmationRequired` sem ele). O valor é o nome completo do registro.\n- `Idempotency-Key` (1 a 128 caracteres de `[A-Za-z0-9._:-]`, 24 h por conta): repetir a\n mesma chave com o mesmo corpo devolve o resultado original; a mesma chave com outro corpo\n é `422 IdempotencyKeyReused`.\n\n**Ambiente e ritmo.** Fora de produção toda escrita responde `403 NonProductionBlocked`\n(salvo zonas liberadas por configuração). Leitura 60/min e escrita 10/min por conta\n(`429 RateLimited` com `Retry-After`). Todo registro criado leva o comentário\n`asender:<id da chave>` e a tentativa entra no histórico (`GET /v1/dns/changes`).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_DnsDryRun"
          },
          {
            "$ref": "#/components/parameters/platform_DnsConfirmar"
          },
          {
            "$ref": "#/components/parameters/platform_DnsIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_DnsRecordCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Com `dry_run`, o plano (nada foi alterado).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsPlan"
                }
              }
            }
          },
          "201": {
            "description": "Registro criado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "record"
                  ],
                  "properties": {
                    "record": {
                      "$ref": "#/components/schemas/platform_DnsRecord"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DnsForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DnsNotFound"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/platform_DnsRejected"
          },
          "428": {
            "$ref": "#/components/responses/platform_DnsConfirmationRequired"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DnsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "dnsPubH.CreateRecord",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dns/zones/{zona}/records/{rid}": {
      "patch": {
        "operationId": "PublicUpdateDnsRecord",
        "x-asender-authz": "apikey",
        "tags": [
          "public-dns"
        ],
        "summary": "Altera um registro de DNS (parcial).",
        "description": "Escopo `dns:write` (só chave). Envie ao menos um de `content`, `ttl`, `priority`, `proxied`,\n`comment`; `type` e `name` são imutáveis (apague e recrie). Os controles de escrita\n(`dry_run`, `X-Asender-Confirmar`, `Idempotency-Key`), o ritmo e o bloqueio fora de produção\nsão os de `POST /v1/dns/zones/{zona}/records`. Mudar `MX`, SPF ou DMARC do apex exige\n`X-Asender-Confirmar` com o nome do registro (`428` sem ele).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_DnsDryRun"
          },
          {
            "$ref": "#/components/parameters/platform_DnsConfirmar"
          },
          {
            "$ref": "#/components/parameters/platform_DnsIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_DnsRecordUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registro alterado (`record`), ou o plano quando há `dry_run`.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "record"
                      ],
                      "properties": {
                        "record": {
                          "$ref": "#/components/schemas/platform_DnsRecord"
                        }
                      }
                    },
                    {
                      "$ref": "#/components/schemas/platform_DnsPlan"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DnsForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DnsNotFound"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/platform_DnsRejected"
          },
          "428": {
            "$ref": "#/components/responses/platform_DnsConfirmationRequired"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DnsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "dnsPubH.UpdateRecord",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "PublicDeleteDnsRecord",
        "x-asender-authz": "apikey",
        "tags": [
          "public-dns"
        ],
        "summary": "Apaga um registro de DNS.",
        "description": "Escopo `dns:write` (só chave). **Exige** `X-Asender-Confirmar` com o nome completo do registro\n(`428 ConfirmationRequired` sem ele); use `dry_run=1` antes para ver o que seria apagado.\nA resposta traz o registro como era (`deleted`) e o histórico guarda o `before`, para recriá-lo.\n`NS` no apex não pode ser apagado (`422 ProtectedRecord`).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_DnsDryRun"
          },
          {
            "$ref": "#/components/parameters/platform_DnsConfirmar"
          },
          {
            "$ref": "#/components/parameters/platform_DnsIdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Apagado (`deleted`), ou o plano quando há `dry_run`.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "deleted"
                      ],
                      "properties": {
                        "deleted": {
                          "$ref": "#/components/schemas/platform_DnsRecord"
                        }
                      }
                    },
                    {
                      "$ref": "#/components/schemas/platform_DnsPlan"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DnsForbidden"
          },
          "404": {
            "$ref": "#/components/responses/platform_DnsNotFound"
          },
          "422": {
            "$ref": "#/components/responses/platform_DnsRejected"
          },
          "428": {
            "$ref": "#/components/responses/platform_DnsConfirmationRequired"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DnsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "dnsPubH.DeleteRecord",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dns/changes": {
      "get": {
        "operationId": "PublicListDnsChanges",
        "x-asender-authz": "apikey",
        "tags": [
          "public-dns"
        ],
        "summary": "Histórico das alterações de DNS da conta.",
        "description": "Escopo `dns:read` (só chave). Histórico append-only de **toda tentativa de escrita** (`ok`,\n`failed` ou `dry_run`), do mais novo ao mais antigo, com `before`/`after`, o ator (`actor`: id da\nchave ou do usuário) e a origem (`source`: `tela`, `api` ou `mcp`). Parâmetro de consulta\ndesconhecido é `400`.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "name": "zone",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Id da zona (filtra o histórico)."
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Alterações e total.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsChanges"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_DnsForbidden"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_DnsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "dnsPubH.ListChanges",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/account": {
      "get": {
        "operationId": "PublicGetAccount",
        "x-asender-authz": "apikey",
        "tags": [
          "public-account"
        ],
        "summary": "Conta da API key usada (ou do app OAuth, com `ClientId`), mais o contexto da credencial.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "TenantId": {
                          "type": "string",
                          "examples": [
                            "acc_demo_sp"
                          ]
                        },
                        "Name": {
                          "type": "string"
                        },
                        "Plan": {
                          "type": "string",
                          "description": "Vem vazio hoje: o core não expõe plano no payload de tenant."
                        },
                        "Status": {
                          "type": "string",
                          "enum": [
                            "active"
                          ]
                        },
                        "ApiKeyId": {
                          "type": "string"
                        },
                        "ApiKeyScope": {
                          "type": "string",
                          "description": "Escopos da chave unidos por vírgula (`email:send,email:read`)."
                        },
                        "ClientId": {
                          "type": "string",
                          "description": "Só no token OAuth (escopo `account:read`): o client_id do app."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "accountH.Get",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/account/usage": {
      "get": {
        "operationId": "PublicGetUsage",
        "x-asender-authz": "apikey",
        "tags": [
          "public-account"
        ],
        "summary": "Contadores de consumo do tenant no período corrente.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "x-asender-divergence": "**Histórico — CORRIGIDO durante esta rodada, mantido por rastreabilidade.**\nO `asender_plataforma_api` respondia `404 NotFound` quando o tenant não tinha\nlinha em `core.usage_counters` (o caso de TODO tenant do seed), e este\nhandler — que só trata 404 em `GET /v1/account`, não aqui — traduzia\nisso em **502 \"Could not load usage.\"**, como se o core estivesse fora.\nO core passou a devolver contadores ZERADOS nesse caso, e a rota\nresponde 200. O `502` abaixo continua documentado porque segue sendo a\nresposta quando o core está de fato indisponível.\n",
        "responses": {
          "200": {
            "description": "Consumo do período. Tenant sem nenhum envio devolve os contadores em\nzero, com `UpdatedAt: null` — ausência de linha é consumo zero, não\nausência de recurso.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Usage": {
                          "$ref": "#/components/schemas/platform_Usage"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_TenantSuspended"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "description": "Core fora **ou** tenant sem contador de uso (ver `x-asender-divergence`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "contadores de consumo do tenant da API key (mês, enviados, quota).\n\nOnde é usada: superfície pública `/v1`, consumida por integrações de cliente.\n\nSaídas: 200 com `{Usage:{Period,EmailsSent,SmsSent,PushSent,ApiCalls}}`; 404 se o tenant da API key não existe mais no core; 502 se o core está fora. DEFEITO QUE ISTO TAMBÉM FECHA (item 7 do briefing Y2, metade da borda): o core respondia 404 quando não havia linha em `core.usage_counters` — e o seed não cria nenhuma —, e este handler traduzia QUALQUER erro do upstream em 502. Ou seja: a rota respondia \"serviço indisponível\" para todo tenant que ainda não enviou nada, mandando o alerta para o time errado. O 404 do core agora só significa \"tenant inexistente\" (a correção principal está em asender_plataforma_api/internal/service/usage.Get) e é traduzido como 404, não 502. DEFEITO QUE ISTO FECHA (T3 §3.6): o payload do core saía CRU sob `Usage` (`{\"Usage\":{\"sent\":42}}`), fora do contrato PascalCase da API pública (API_DESIGN.md §51). O cliente que segue o contrato lê `Usage.Sent` e recebe `undefined` — mesma classe de divergência de fronteira do GET /api/tenants.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "accountH.Usage",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "PublicGetMe",
        "x-asender-authz": "apikey",
        "tags": [
          "public-account"
        ],
        "summary": "Quem autorizou o app OAuth, em que conta e com que escopos (só token OAuth, escopo openid).",
        "security": [
          {
            "OAuthBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK (JSON em snake_case, sem RequestId)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string",
                          "description": "Só quando o token foi emitido com o escopo email."
                        },
                        "name": {
                          "type": "string",
                          "description": "Só quando o token foi emitido com o escopo profile."
                        }
                      }
                    },
                    "account": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "examples": [
                            "acc_demo_sp"
                          ]
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "client_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Sem o escopo openid, chave sk_* (rota só de OAuth) ou usuário sem acesso à conta."
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "identidade e conta SEMPRE do token verificado (nunca de query/header); o nome da conta vem do core. `email`/`name` só aparecem se o token os trouxe (escopos email/profile). Snake_case, sem pascalizar (rota nova do recurso).\n\nOnde é usada: GET /v1/me, depois do PublicAuth + dados.ExigirProduto(\"\").",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "meOAuthH.Get",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/logs": {
      "get": {
        "operationId": "PublicListLogs",
        "x-asender-authz": "apikey",
        "tags": [
          "public-logs"
        ],
        "summary": "Log das requisições feitas à API com as chaves da conta (sem corpos).",
        "description": "Lista, do mais novo para o mais antigo, as requisições que **a sua conta**\nfez à API pública — inclusive as recusadas (400, 401 com chave da conta,\n403, 429). A listagem **não traz os corpos**; use `GET /v1/logs/{id}`.\n\nO tenant é o da API key; não há parâmetro de conta (`tenant_id` na query é\ndescartado). Paginação por cursor: repita a chamada com `after=<next>`\nenquanto `has_more` for `true`.\n\nEscopo: `logs:read`. Teto próprio de **5 req/s** por chave, além do geral.\nServiço de logs fora do ar: **503 `ServiceUnavailable`** com `Retry-After`;\no resto da API não é afetado.\n\n**Formato:** o corpo é repassado do serviço de logs **como veio**, em\n`snake_case` (diferente do `PascalCase` das demais rotas `/v1`), e os\nerros 400/404 vêm no envelope `{\"error\":{\"type\",\"code\",\"message\"}}`\n(ver `LogsErrorEnvelope`).",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Itens por página."
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco (`next` da página anterior): devolve os registros mais antigos. Adulterado ou de outra conta → 400. Não combine com `before`."
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco: devolve os mais novos. `after` e `before` juntos → 400."
          },
          {
            "name": "status_class",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "4xx,5xx"
              ]
            },
            "description": "Lista separada por vírgula de `2xx`, `3xx`, `4xx`, `5xx`."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 100,
              "maximum": 599
            },
            "description": "Status HTTP exato."
          },
          {
            "name": "method",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "POST"
              ]
            },
            "description": "Verbo HTTP."
          },
          {
            "name": "path",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "/v1/emails/send"
              ]
            },
            "description": "Padrão de rota exato (não o caminho com ids)."
          },
          {
            "name": "api_key_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Id de uma ou mais chaves da conta, separados por vírgula."
          },
          {
            "name": "error_code",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "ValidationError"
              ]
            },
            "description": "Código do envelope de erro devolvido."
          },
          {
            "name": "idempotency_key",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Chave de idempotência efetiva da requisição."
          },
          {
            "name": "message_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Id de mensagem devolvido pelo envio (liga o log ao envio)."
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "live",
                "test"
              ]
            },
            "description": "Modo da chave usada."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339. Padrão: agora menos 24 h."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339. Padrão: agora. Janela maior que 31 dias é reduzida a 31 dias antes de `until`."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_LogsList"
                },
                "example": {
                  "object": "list",
                  "has_more": true,
                  "next": "cur_eyJ0IjoiMjAyNi0xMC0wN1QxMjowMDowMFoiLCJpIjoibG9nXzAxIn0",
                  "data": [
                    {
                      "id": "log_3f9a1c0b2d4e5f60",
                      "object": "log",
                      "created_at": "2026-10-07T12:00:01Z",
                      "method": "POST",
                      "path": "/v1/emails/send",
                      "route": "/v1/emails/send",
                      "status": 202,
                      "duration_ms": 41,
                      "mode": "test",
                      "api_key": {
                        "id": "key_9",
                        "name": "producao"
                      },
                      "idempotency_key": "pedido-1042",
                      "error_code": "",
                      "message_ids": [
                        "msg_01HXYZ"
                      ],
                      "request_id": "req_abc123"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_LogsValidation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`403 InsufficientScope` (chave sem `logs:read`) ou `403 TenantSuspended`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_LogsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "logsH.List",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/logs/{id}": {
      "get": {
        "operationId": "PublicGetLog",
        "x-asender-authz": "apikey",
        "tags": [
          "public-logs"
        ],
        "summary": "Detalhe de uma requisição, com os corpos já redigidos.",
        "description": "Traz `request_body` e `response_body` **depois da redação**: tokens de API,\nJWT, senhas, cabeçalhos de autorização, códigos de verificação (OTP),\ncartões e CPF não são gravados; destinatários aparecem mascarados. Corpos\nacima de 16 KiB são cortados (`*_truncated: true`). `redaction_version`\ndiz qual conjunto de regras valeu quando o log foi gravado.\n\nId de **outra conta** e id inexistente respondem o mesmo **404**, com o\nmesmo corpo. Escopo: `logs:read`; mesmo teto de 5 req/s.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^log_[0-9a-f]{16}$",
              "examples": [
                "log_3f9a1c0b2d4e5f60"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_LogDetalhe"
                },
                "example": {
                  "id": "log_3f9a1c0b2d4e5f60",
                  "object": "log",
                  "created_at": "2026-10-07T12:00:01Z",
                  "method": "POST",
                  "path": "/v1/emails/send",
                  "route": "/v1/emails/send",
                  "status": 202,
                  "duration_ms": 41,
                  "mode": "test",
                  "api_key": {
                    "id": "key_9",
                    "name": "producao"
                  },
                  "idempotency_key": "pedido-1042",
                  "error_code": "",
                  "message_ids": [
                    "msg_01HXYZ"
                  ],
                  "request_id": "req_abc123",
                  "service": "plataforma",
                  "query": "",
                  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
                  "request_body": {
                    "from": "no-reply@empresa.com.br",
                    "to": [
                      "c***@cliente.com.br"
                    ],
                    "subject": "Pedido 1042",
                    "text": "[redigido]"
                  },
                  "request_body_truncated": false,
                  "response_body": {
                    "Messages": [
                      {
                        "Id": "msg_01HXYZ"
                      }
                    ]
                  },
                  "response_body_truncated": false,
                  "request_size": 212,
                  "response_size": 96,
                  "redaction_version": 1
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`403 InsufficientScope` (chave sem `logs:read`) ou `403 TenantSuspended`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_LogsNotFound"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_LogsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "logsH.Detalhe",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/login": {
      "post": {
        "operationId": "BffLogin",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Autentica e devolve o token de sessão.",
        "description": "Corpo aceito em snake_case (`{\"email\",\"password\"}`) porque é o que o\n`asender_auth_api` define e o proxy repassa; a RESPOSTA é PascalCase.\n\nCom 2FA habilitado a resposta é 200 com `Token:\"\"` e\n`TwoFactorRequired:true` — explícito, para o chamador não confundir com\nupstream quebrado.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "examples": [
                      "demo@asender.local"
                    ]
                  },
                  "password": {
                    "type": "string",
                    "format": "password"
                  }
                }
              }
            }
          }
        },
        "x-asender-divergence": "Oráculo de tempo: login de usuário existente leva ~200 ms (bcrypt) e de\ninexistente ~1 ms. Permite enumerar contas (RELATORIO-FINAL §3 item 9).\n",
        "responses": {
          "200": {
            "description": "Sessão criada, ou 2FA pendente.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Token": {
                          "type": "string",
                          "description": "JWT. Vazio quando `TwoFactorRequired`."
                        },
                        "UserId": {
                          "type": "string"
                        },
                        "UserEmail": {
                          "type": "string"
                        },
                        "ExpiresAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "TwoFactorRequired": {
                          "type": "boolean"
                        },
                        "TwoFactorMethod": {
                          "type": "string",
                          "enum": [
                            "email",
                            "totp"
                          ],
                          "description": "Canal do 2º fator quando `TwoFactorRequired`: `email` (padrão) ou `totp`."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Conta bloqueada ou desabilitada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.Login",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/register": {
      "post": {
        "operationId": "BffRegister",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Cria o usuário e, quando pode, o tenant raiz dele.",
        "description": "**O status distingue os dois desfechos, e isso é contrato.**\n\n`201` = usuário E conta criados. `202` = usuário criado, conta **não** —\ncom um `Reason` do conjunto fechado `DomainClaimed` | `TenantNameTaken` |\n`ProvisioningFailed` | `Invited` (cadastro por convite, sem equipe de\npropósito: o próximo passo é o aceite). O cliente ramifica pelo `Reason` e leva a pessoa ao\nonboarding; tratar 202 como sucesso liso produz conta ÓRFÃ (relato do\ndono, 2026-09-17): usuário autenticado que não pertence a conta nenhuma,\ninvisível para qualquer admin.\n\n`DomainClaimed` significa que o domínio do e-mail pertence a uma conta que\njá o verificou. Não nasce workspace paralelo: nasce um pedido de entrada,\ne `AccessRequestId` o identifica.\n\nO campo `Note` em prosa **saiu**: texto serve para mostrar, não para\nramificar.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "Email",
                  "Password"
                ],
                "properties": {
                  "Email": {
                    "type": "string",
                    "format": "email"
                  },
                  "Password": {
                    "type": "string",
                    "minLength": 8
                  },
                  "Name": {
                    "type": "string"
                  },
                  "TenantName": {
                    "type": "string"
                  },
                  "ConviteToken": {
                    "type": "string",
                    "description": "Token do link de convite. Presente = cadastro de convidado:\no convite é conferido ANTES de criar o usuário (pendente e\npara o mesmo e-mail, sem diferença de caixa); válido → `202`\n`Reason: Invited`, sem equipe e sem consulta de domínio.\nSubstitui o antigo `Convidado` (bool), hoje recusado como\ncampo desconhecido."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Usuário e conta criados.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "UserId",
                        "UserEmail",
                        "Tenant"
                      ],
                      "properties": {
                        "UserId": {
                          "type": "string"
                        },
                        "UserEmail": {
                          "type": "string"
                        },
                        "Tenant": {
                          "$ref": "#/components/schemas/platform_Tenant"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Usuário criado, conta NÃO. O `Reason` diz por quê e o cliente\nencaminha para o onboarding.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "UserId",
                        "UserEmail",
                        "Tenant",
                        "Reason"
                      ],
                      "properties": {
                        "UserId": {
                          "type": "string"
                        },
                        "UserEmail": {
                          "type": "string"
                        },
                        "Tenant": {
                          "type": "null"
                        },
                        "Reason": {
                          "type": "string",
                          "enum": [
                            "DomainClaimed",
                            "TenantNameTaken",
                            "ProvisioningFailed",
                            "Invited"
                          ]
                        },
                        "ClaimedBy": {
                          "type": "string",
                          "description": "Nome da conta dona do domínio (só em `DomainClaimed`)."
                        },
                        "AccessRequestId": {
                          "type": "string",
                          "description": "Pedido de entrada aberto (só em `DomainClaimed`). Ausente\nquando o domínio é reivindicado mas o pedido não pôde ser\naberto — a pessoa ainda vai ao onboarding e pede à mão."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`400 ValidationError` — JSON inválido, campo desconhecido, e-mail ou\nsenha fora da regra. Com `ConviteToken`: `InviteInvalid` (convite\ninexistente, aceito ou expirado) ou `InviteEmailMismatch` (e-mail do\ncadastro diferente do convite). Nos dois, nenhum usuário é criado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/platform_Conflict"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.Register",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/2fa": {
      "post": {
        "operationId": "BffTwoFactorLogin",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Segunda etapa do login — confirma o código do segundo fator.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "`TwoFactorFailed` (código errado), `TwoFactorSuperseded` (código\nsubstituído por um mais novo — use o e-mail mais recente),\n`TwoFactorCodeExpired` (código vencido — peça outro) ou\n`TwoFactorExpired` (o ticket do login venceu — recomece o login)."
          },
          "429": {
            "description": "`TooManyAttempts` — freio do código; `Retry-After` (segundos) e\n`Details.RetryAfterSeconds` quando o prazo é conhecido."
          },
          "502": {
            "description": "`InternalError` — o auth não respondeu."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "troca o código do autenticador pela sessão.\n\nOnde é usada: tela `/2fa`, para onde o login manda quem tem segundo fator.\n\nEfeitos: cria sessão no asender-auth. # Por que esta rota é PÚBLICA Quem chega aqui ainda não tem sessão — é justamente o que ela está tentando obter. Exigir sessão tornaria o 2FA impossível de completar: a armadilha de aplicar a mesma guarda em toda rota \"porque é mais seguro\". O que protege é o par (TICKET de desafio, código): o ticket é assinado pelo Login e prova que a senha bateu neste fluxo (um `user_id` cru não abre mais nada — rodada 3, H-1), e o código vale 30 segundos. Sem esta rota, o login de quem tem 2FA ficava sem passo seguinte — a conta ficava inacessível pelo painel.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.TwoFactor",
        "x-asender-fonte": "spec+codigo"
      },
      "get": {
        "operationId": "BffTwoFactorState",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Estado do segundo fator do usuário da sessão.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "diz se a conta da sessão tem TOTP CONFIRMADO.\n\nOnde é usada: tela de segurança do painel.\n\nEfeitos: uma leitura no asender-auth. Responde o estado CONFIRMADO, e não \"existe segredo\": um setup interrompido deixa segredo gravado sem confirmação, e mostrar \"2FA ligado\" aí faria a pessoa acreditar numa proteção que o login não exige.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.EstadoDoSegundoFator",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/2fa/resend": {
      "post": {
        "operationId": "BffTwoFactorResend",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Reenvia o código de e-mail do 2º fator (método `email`).",
        "description": "Público como o `/api/auth/2fa`: a identidade vem do TICKET de desafio\n(corpo `{TwoFactorToken}`). 401 sem ticket; 429 no cap de emissões.",
        "responses": {
          "200": {
            "description": "Código reenviado."
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "429": {
            "description": "Muitos pedidos de código."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.Reenviar2FA",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/ativacao/estado": {
      "post": {
        "operationId": "BffAtivacaoEstado",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Passo da ativação obrigatória do 2º fator (conta gerenciada, primeiro acesso).",
        "description": "Pública: quem chega não tem sessão. Credencial = `Ticket` de ativação (o\n`EnrollmentToken` do login), que NÃO vale como sessão. 200 `{Etapa, MustReset}`.",
        "responses": {
          "200": {
            "description": "Etapa `senha` ou `fator`."
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "description": "`ActivationExpired` — refazer o login."
          },
          "409": {
            "description": "`NothingToActivate`."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.AtivacaoEstado",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/ativacao/senha": {
      "post": {
        "operationId": "BffAtivacaoSenha",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Troca a senha temporária (passo 1 da ativação).",
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "description": "`ActivationExpired`."
          },
          "409": {
            "description": "`NothingToActivate`."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.AtivacaoSenha",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/ativacao/totp/setup": {
      "post": {
        "operationId": "BffAtivacaoTotpSetup",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Gera o segredo/QR do app autenticador (passo 2). Exige a senha já trocada.",
        "responses": {
          "200": {
            "description": "`{Secret, OtpauthUrl}`."
          },
          "401": {
            "description": "`ActivationExpired`."
          },
          "409": {
            "description": "`PasswordChangeRequired` ou `NothingToActivate`."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.AtivacaoTOTPSetup",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/ativacao/totp/confirm": {
      "post": {
        "operationId": "BffAtivacaoTotpConfirm",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Confirma o código do app e devolve a sessão plena (passo 3).",
        "responses": {
          "200": {
            "description": "`{Token, UserId, ExpiresAt, RecoveryCodes}`."
          },
          "401": {
            "description": "`TwoFactorFailed` ou `ActivationExpired`."
          },
          "409": {
            "description": "`PasswordChangeRequired` ou `NothingToActivate`."
          },
          "429": {
            "description": "Freio do 2º fator."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.AtivacaoTOTPConfirmar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/verify": {
      "post": {
        "operationId": "BffVerifyEmail",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Confirma o e-mail a partir do token do link.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "link inválido",
            "expirado ou já usado (respostas iguais)": null
          },
          "409": {
            "description": "email_em_uso — o endereço já está verificado em outra conta (token intacto)"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "repassa o token do link ao asender-auth.\n\nOnde é usada: tela `/verify`, com o token da URL.\n\nEfeitos: carimba `email_verified_at` no auth. # Também é PÚBLICA Quem abre o link do e-mail pode estar em outro navegador, ou nem ter sessão. Exigir login para verificar o e-mail cria a dependência circular clássica — e a pessoa que mais precisa verificar é justamente a que ainda não conseguiu entrar.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.Verify",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/verify/resend": {
      "post": {
        "operationId": "BffVerifyResend",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Reenvia o link de confirmação sem sessão (202 neutro, teto por IP).",
        "responses": {
          "202": {
            "description": "aceito (mesma resposta exista ou não pendência)"
          },
          "400": {
            "description": "e-mail ausente ou malformado"
          },
          "429": {
            "description": "teto por IP"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "pede um link novo de confirmação para um endereço, sem sessão.\n\nOnde é usada: tela `/verify` quando o link falhou e o navegador não tem sessão.\n\nEfeitos: no máximo um e-mail (guard do piso 18 no auth). # PÚBLICA, resposta NEUTRA, no MESMO teto por IP 202 idêntico exista ou não pendência para o endereço (o auth só envia se houver pendência viva). O 429 é por IP, igual para qualquer endereço.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.VerifyResend",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/password-reset": {
      "post": {
        "operationId": "BffPasswordReset",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Inicia a recuperação de senha (resposta neutra, não vaza se a conta existe).",
        "responses": {
          "200": {
            "description": "aceito (mesma resposta exista ou não o e-mail)"
          },
          "400": {
            "description": "e-mail ausente ou malformado"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "repassa o e-mail ao asender_auth_api, que gera o token e dispara o e-mail com o link `/reset-password?token=...`.\n\nOnde é usada: tela pública `/forgot-password`.\n\nEfeitos: um e-mail de recuperação (o guard do auth decide se sai fora de prd, piso 18). Nenhuma sessão é criada. # PÚBLICA e com resposta NEUTRA Quem pede recuperação não tem sessão — é o ponto. A resposta é sempre a mesma, exista ou não o e-mail: dizer \"esse e-mail não existe\" entregaria a base de usuários a quem testa endereços (enumeração). O token do link NUNCA volta no corpo — o `AuthClient.PasswordResetRequest` o descarta ainda no cliente; quem recebe o link é a caixa de e-mail. Vive no MESMO teto por IP das demais rotas públicas de auth (server.go), que fecha o brute-force/spam deste endpoint.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.PasswordReset",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/password-reset/send-otp": {
      "post": {
        "operationId": "BffPasswordResetSendOTP",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Dispara o código do 2º fator (canal e-mail) do reset em curso.",
        "description": "Pública como o resto do fluxo de reset: quem esqueceu a senha não tem\nsessão. Recebe o `Token` do reset em andamento e envia o OTP ao e-mail\nprimário do dono do token. **Resposta neutra** — não confirma se o token\nvale. Disparo de e-mail (piso 18) e alvo de spam/amplificação, então no\nMESMO teto de rate-limit por IP das outras rotas de auth públicas.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "Token"
                ],
                "properties": {
                  "Token": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Token do reset em andamento (veio no link do e-mail)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Aceito (resposta neutra `{\"Status\":\"sent\"}`; não confirma o token).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Status": {
                          "type": "string",
                          "example": "sent"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Token ausente."
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.PasswordResetSendOTP",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/password-reset/confirm": {
      "post": {
        "operationId": "BffPasswordResetConfirm",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Redefine a senha pelo token do link (+ código 2FA quando a conta tem segundo fator).",
        "responses": {
          "200": {
            "description": "senha redefinida; sessões do usuário derrubadas"
          },
          "400": {
            "description": "token ou nova senha ausentes"
          },
          "401": {
            "description": "token inválido/expirado, 2FA exigido ou 2FA incorreto (distinguidos pelo Error.Code)"
          },
          "422": {
            "description": "nova senha não atende à política (mínimo 12 + HIBP)"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "troca a senha usando o token do link e (quando a conta tem 2FA) o código do segundo fator, repassando ao asender_auth_api.\n\nOnde é usada: tela pública `/reset-password`, que recebe `?token=` do link do e-mail.\n\nEfeitos: grava a senha nova e DERRUBA todas as sessões abertas do usuário (lá no auth). Nenhuma sessão nova é criada — a pessoa faz login em seguida. # PÚBLICA, e os 401 são separados pelo Code Quem redefine a senha não tem sessão (esqueceu a senha). O auth responde 401 para TRÊS situações distintas — token inválido/expirado, 2FA exigido e 2FA incorreto — cada uma com um `Code` próprio no envelope. Separá-las é o que permite à tela pedir o código quando falta, sem transformar \"código errado\" em \"link inválido\". Senha fraca vem como 400 (mesma política do cadastro: mínimo 12 + HIBP) e sobe como 422 com a razão, para a pessoa saber o que corrigir.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.PasswordResetConfirm",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/invitations/{token}/preview": {
      "get": {
        "operationId": "BffPreviewInvitation",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Prévia do convite pelo token do link — PÚBLICA, teto por IP.",
        "description": "**Pública por desenho**: quem abre o link do convite ainda não tem\nsessão. O token (CSPRNG) é a credencial — só o portador vê o e-mail do\nPRÓPRIO convite. Não devolve token, id da conta nem quem convidou. Está\nna allowlist `publicRoutes` do `routes_test.go`.\n\n`HasAccount` diz se o e-mail do convite já tem conta (a tela oferece\n\"Entrar\" ou \"Criar conta com este e-mail\"). Só é consultado com\n`Status: pending`; nos outros estados, e quando o `asender_auth_api` não\nresponde (degradação graciosa), vem `null`.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Convite encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "Email",
                        "TenantName",
                        "Role",
                        "Status",
                        "HasAccount"
                      ],
                      "properties": {
                        "Email": {
                          "type": "string",
                          "format": "email"
                        },
                        "TenantName": {
                          "type": "string"
                        },
                        "Role": {
                          "type": "string"
                        },
                        "Status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "accepted",
                            "expired",
                            "revoked",
                            "declined"
                          ],
                          "description": "revoked = quem convidou cancelou; declined = o convidado recusou."
                        },
                        "HasAccount": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "`404 NotFound` — token desconhecido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.PreviewConvite",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/public/planos": {
      "get": {
        "operationId": "BffPublicPlanos",
        "x-asender-authz": "public",
        "tags": [
          "bff-public"
        ],
        "summary": "Catálogo único de planos — vitrine pública (ADR-0075).",
        "description": "**Anônima por desenho**: é o que a LP (www.asender.net) e o hub\n(`/contratar`) mostram — nome, descrição, preço, CTA e limites de cada\nplano publicado. Sem tenant, sem sessão, sem segredo. Está na allowlist\n`publicRoutes` do `routes_test.go`.\n\nFonte única: `core.planos` + `core.plano_limites`, lidas do core em\n`GET /v1/planos`. Corpo em **snake_case** (contrato ADR-0075), não o\nPascalCase do resto do `/api` — a mesma forma do core, tipada (campo\ninesperado do core não vaza).\n\n- `preco_centavos: null` = **sob consulta** (nunca 0).\n- `limites` traz todo domínio de `core.plano_limites` do plano; `dia`/`mes`\n `null` = sem limite naquela escala.\n\n**Cache:** memória do BFF por `PLANOS_CACHE_TTL` (60 s) e\n`Cache-Control: public, max-age=60`. **Stale-on-error:** com o core fora,\nserve a última cópia boa (warn no log com `RequestId`); só a primeira\nleitura sem cópia responde **503** (`Cache-Control: no-store`).\n\n**Teto por IP** próprio: `PUBLIC_RATE_LIMIT_PER_IP` (120) por\n`PUBLIC_RATE_LIMIT_WINDOW` (1 min).",
        "responses": {
          "200": {
            "description": "Planos publicados, em `ordem`.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "public, max-age=60"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_CatalogoPlanos"
                },
                "examples": {
                  "catalogo": {
                    "value": {
                      "moeda": "BRL",
                      "planos": [
                        {
                          "slug": "basico",
                          "nome": "Básico",
                          "descricao": "Para pequenas empresas começando",
                          "preco_centavos": 350000,
                          "periodo": "mes",
                          "destaque": false,
                          "cta": "contratar",
                          "ordem": 10,
                          "limites": {
                            "email_corporativo": {
                              "dia": 50,
                              "mes": 250
                            },
                            "email_marketing": {
                              "dia": null,
                              "mes": 5000
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "`503 ServiceUnavailable` — o core não respondeu e não há cópia em\ncache. A LP cai no fallback estático dela.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "planosH.Listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/logout": {
      "post": {
        "operationId": "BffLogout",
        "x-asender-authz": "bearer-unverified",
        "tags": [
          "bff-auth"
        ],
        "summary": "Revoga a sessão do Bearer apresentado. IDEMPOTENTE.",
        "description": "Revoga no `asender_auth_api`; a validação de sessão passou a consultar o\nestado, então o token deixa de valer (401 no `/api/auth/me` seguinte).\n\n**Esta rota fica FORA do grupo `SessionAuth`, de propósito** — é o único\ncaso em `/api/*`. Dentro do grupo, o segundo logout (retry, aba\nduplicada, refresh) morria em 401 no middleware, quebrando a\nidempotência de uma operação que descreve um ESTADO desejado (\"estar\nfora\"), não uma transição.\n\nConsequência observável, e é por isso que a classe declarada é\n`bearer-unverified` e não `session`: o handler exige o header\n`Authorization` (sem ele, 401) mas **não valida a sessão**. Qualquer\nstring não vazia como Bearer — inclusive uma API key ou lixo — recebe\n**200**. Não abre nada (revogar um token que não existe é no-op no\n`asender_auth_api`), mas o 401 desta rota vem do HANDLER, não do middleware:\nquem for auditar a superfície não pode contá-la como autenticada.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Revogado, ou já estava — inclusive para um Bearer que nunca foi sessão.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Status": {
                          "type": "string",
                          "const": "ok"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.Logout",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/me": {
      "get": {
        "operationId": "BffMe",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Principal da sessão.",
        "description": "`TenantId` vem do `asender_auth_api` e **costuma vir vazio** — a resolução\nreal do tenant é feita por rota (`resolveSessionTenant`), a partir dos\nvínculos no core. Não use este campo como escopo.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "UserId": {
                          "type": "string"
                        },
                        "UserEmail": {
                          "type": "string"
                        },
                        "TenantId": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.Me",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "operationId": "BffUpdateMe",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Altera o NOME do usuário da sessão.",
        "description": "Só `Name`. `Email` no corpo → `400 UseEmailsEndpoint` (achado F1 da \"Minha conta\"): o e-mail só muda por `/api/me/emails*`, com reautenticação e verificação. Campo desconhecido → 400.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "Name"
                ],
                "properties": {
                  "Name": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "`UseEmailsEndpoint` (veio `Email`), `ValidationError` (Name vazio) ou campo desconhecido."
          },
          "401": {
            "description": "credencial ausente ou inválida"
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.UpdateMe",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/me/verify/resend": {
      "post": {
        "operationId": "BffResendVerification",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Reenvia o e-mail de verificação do usuário da sessão.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "pede ao asender_auth_api um link de verificação novo para o usuário da sessão.\n\nOnde é usada: aviso \"confirme seu e-mail\" do painel.\n\nEfeitos: um e-mail (o guard do auth decide se ele sai fora de prd). O 429 do upstream é REPASSADO como 429: quem pediu demais precisa saber que basta esperar, e um 502 aqui mandaria a pessoa procurar defeito onde não há.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.ResendVerification",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/me/emails": {
      "get": {
        "operationId": "BffListMyEmails",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "E-mails de recuperação do usuário logado.",
        "description": "O `sub` vem SEMPRE da sessão (anti-IDOR): não há como listar os e-mails de\noutro usuário. Repassa ao `asender_auth_api` `/v1/users/{sub}/emails`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK (corpo repassado do asender_auth_api)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailsH.List",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffAddMyEmail",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Acrescenta um e-mail de recuperação ao usuário logado.",
        "description": "O `sub` vem da sessão. Corpo repassado ao asender_auth_api; JSON\nmalformado é recusado AQUI com 400 (não vira 502).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK (status/corpo repassados do asender_auth_api)."
          },
          "400": {
            "description": "Corpo não é JSON válido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailsH.Add",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/me/emails/{id}": {
      "delete": {
        "operationId": "BffRemoveMyEmail",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Remove um e-mail de recuperação do usuário logado.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK (status/corpo repassados do asender_auth_api)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailsH.Remove",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/me/emails/{id}/primary": {
      "post": {
        "operationId": "BffSetMyPrimaryEmail",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Promove um e-mail do usuário logado a primário.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK (status/corpo repassados do asender_auth_api)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailsH.Primary",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/me/emails/{id}/verify/resend": {
      "post": {
        "operationId": "BffResendMyEmailVerify",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Reenvia a verificação de um e-mail do usuário logado.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK (status/corpo repassados do asender_auth_api)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "emailsH.Resend",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/2fa/setup": {
      "post": {
        "operationId": "BffTwoFactorSetup",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Inicia a ativação do segundo fator.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "começa a ativação e devolve o segredo e a URL do QR.\n\nOnde é usada: tela de segurança.\n\nEfeitos: grava o segredo PENDENTE de confirmação. # A senha é a invariante do piso 17, e ela para AQUI se faltar Para mexer num fator é preciso apresentar um fator diferente dele. O serviço recusa senha vazia, e o gateway recusa antes — não por desconfiança do serviço, mas porque um corpo sem senha é pedido malformado, e mandá-lo adiante gastaria uma viagem para receber a mesma recusa.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.LigarSegundoFator",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/2fa/verify": {
      "post": {
        "operationId": "BffTwoFactorConfirm",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Confirma a ativação do segundo fator.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "conclui a ativação com um código do aplicativo.\n\nOnde é usada: tela de segurança, depois de ler o QR.\n\nEfeitos: o login passa a EXIGIR o segundo fator.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.ConfirmarSegundoFator",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/2fa/disable": {
      "post": {
        "operationId": "BffTwoFactorDisable",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Troca o app pelo e-mail (não desliga o 2º fator).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "remove o TOTP da conta da sessão.\n\nOnde é usada: tela de segurança.\n\nEfeitos: o login deixa de exigir o segundo fator. Exige a SENHA, e não o código: aceitar o próprio TOTP para removê-lo faria o fator se autorizar sozinho.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.DesligarSegundoFator",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/sessions": {
      "get": {
        "operationId": "BffListSessions",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Dispositivos e sessões ativas do usuário.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "lista onde a conta do usuário da sessão está aberta.\n\nOnde é usada: tela de segurança do painel.\n\nEfeitos: uma leitura no asender-auth. O token repassado é o da REQUISIÇÃO, e é ele que define de quem é a lista. Não existe parâmetro de usuário: um `?user_id=` seria a rota mais barata do sistema para descobrir de onde outra pessoa se conecta.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.Sessoes",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/sessions/{id}": {
      "delete": {
        "operationId": "BffRevokeSession",
        "x-asender-authz": "session",
        "tags": [
          "bff-auth"
        ],
        "summary": "Revoga uma sessão específica.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "derruba uma sessão do próprio usuário.\n\nOnde é usada: botão \"remover\" da tela de segurança.\n\nEfeitos: a sessão para de autenticar imediatamente. # Por que a sessão de suporte NÃO passa por aqui A sessão de suporte é somente leitura (ADR-0017), e o middleware de suporte já recusa escrita. Isto é uma escrita — e derrubar o dispositivo de um cliente durante uma sessão de suporte é exatamente o poder que o ADR tira de quem entra na conta alheia.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.RevogarSessao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants": {
      "get": {
        "operationId": "BffListTenants",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Vínculos de tenant do usuário logado.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Tenants": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_Tenant"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "os vínculos de tenant do usuário logado, para o seletor de conta.\n\nOnde é usada: dashboard (topbar/tenant-switcher, settings/account) e backoffice (useTenantForest).\n\nSaídas: 200 com `{Tenants:[{Id,Name,Slug,Status,...}]}`; 502 se o core está fora. DEFEITO QUE ISTO FECHA (T3 §3.5): a rota devolvia o payload do core CRU (`tenants[].id`), enquanto TODO o resto de `/api/*` — inclusive a rota vizinha `/api/tenants/tree` — responde PascalCase (API_DESIGN.md §51). Quem seguia o contrato lia `Tenants` e recebia `undefined`; quem \"fazia funcionar\" passava a ler `tenants` e ficava preso ao formato interno do core.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "tenantsH.List",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateTenant",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Cria um tenant RAIZ pertencente ao usuário logado.",
        "description": "**Allowlist tipada de dois campos.** `parent_id`, `monthly_quota`,\n`status`, `plan`, `owner_user_id` e `id` são IGNORADOS e registrados —\nem nível ERROR, como tentativa de escalonamento de privilégio. O\n`owner_user_id` vem sempre do principal verificado.\n\nIgnorar (em vez de 422) preserva chamadores antigos que mandam\n`owner_user_id`; ignorar aqui **não é engolir**: fica no log com o\nusuário que enviou.\n\nPara pendurar um tenant sob outro use `POST /api/tenants/{id}/children`\n(autoriza o pai) ou `PATCH /api/tenants/{id}/parent` (autoriza os dois\nlados). É o único caminho autorizado para mexer na árvore.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "Name"
                ],
                "properties": {
                  "Name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "Slug": {
                    "type": "string"
                  }
                },
                "additionalProperties": {
                  "description": "Aceito no wire e DESCARTADO; ver descrição."
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Tenant criado. **O objeto vem NO TOPO, sem a chave `Tenant`** — o\n`asender_plataforma_api` responde o tenant sem chave de recurso nesta rota e o\nBFF só pascaliza o que recebeu. É inconsistente com\n`POST /api/tenants/{id}/children` e com `POST /api/auth/register`,\nque devolvem `{\"Tenant\":{...}}`. Documentado como está no ar.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "$ref": "#/components/schemas/platform_Tenant"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "tenantsH.Create",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/contratacao": {
      "post": {
        "operationId": "BffContratar",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Auto-contratação de plano (self-service); registra pagamento pendente.",
        "description": "TRANSVERSAL (fora do grupo `marketing`): é a rota que tira o usuário do\ndead-end do hub. Concede a governança `admin:owner` da conta na hora (é\ngrátis) e registra a INTENÇÃO de pagar — o acesso PAGO só é liberado\nquando o pagamento é aprovado no fluxo de billing manual (MVP).\n\nCorpo NÃO-estrito: campos extras (um `user_id` malicioso, por ex.) são\nIGNORADOS — o dono vem sempre da sessão verificada (anti-IDOR). O tenant\né resolvido pela sessão (header `X-Asender-Tenant` quando o usuário\npertence à conta), nunca do corpo. Conta com OUTRO dono é recusada antes\nde qualquer escrita.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "Produtos"
                ],
                "properties": {
                  "Produtos": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "enum": [
                        "mail",
                        "marketing",
                        "crm",
                        "pages"
                      ]
                    },
                    "description": "Ferramentas a contratar. \"full\" é expandido no front antes de enviar."
                  },
                  "Plano": {
                    "type": "string",
                    "enum": [
                      "basico",
                      "pro",
                      "business"
                    ],
                    "default": "basico"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pagamento pendente registrado.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Status": {
                          "type": "string",
                          "example": "pendente"
                        },
                        "PagamentoId": {
                          "type": "string"
                        },
                        "Produtos": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "Plano": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "422": {
            "description": "Produto/plano inválido, ou nenhum produto escolhido."
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "contratacaoH.Contratar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/tree": {
      "get": {
        "operationId": "BffTenantTree",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Subárvore do tenant corrente, em pré-ordem.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "depth",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "TenantId": {
                          "type": "string"
                        },
                        "Tree": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_TenantNode"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "a subárvore do tenant corrente, para o seletor de tenant e a tela de hierarquia.\n\nOnde é usada: dashboard (settings/tenants) e backoffice.\n\nEntradas: `depth` — inteiro positivo; valor não numérico é 422, não \"sem limite\" silencioso.\n\nSaídas: 200 com `{TenantId, Tree:[...]}`; 404 se o tenant pedido não é acessível.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "treeH.Tree",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/children": {
      "post": {
        "operationId": "BffCreateChildTenant",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Cria uma sub-conta sob um tenant administrado pelo usuário.",
        "description": "Autoriza `{id}` por vínculo direto **ou herdado** (ser membro de um\nancestral manda na subárvore). `MonthlyQuota` ausente/nulo = herda do\nancestral mais próximo com valor. O usuário da SESSÃO vira o `owner` da\nsub-conta (nunca um campo do corpo): ela nasce com governança e as\nferramentas padrão, e aparece no lançador e no admin.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "Name"
                ],
                "additionalProperties": false,
                "properties": {
                  "Name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "Slug": {
                    "type": "string"
                  },
                  "MonthlyQuota": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sub-conta criada.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Tenant": {
                          "$ref": "#/components/schemas/platform_TenantNode"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Papel efetivo na conta não governa a árvore (`GovernanceRole`): só\n`owner`/`admin` (direto ou do ancestral concedente) criam, movem ou\nmudam cota de sub-conta — `developer`/`viewer` não (2026-10-04).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "$ref": "#/components/responses/platform_Conflict"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "treeH.CreateChild",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/parent": {
      "patch": {
        "operationId": "BffMoveTenant",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Move o tenant para outro pai, importa uma conta como sub-conta, ou promove a raiz.",
        "description": "**Autoriza os TRÊS lados** (ADR-0098): `{id}` (quem entrega), o pai\nATUAL de `{id}` quando ele não é raiz (quem perde — sem isto o admin\ndireto de uma sub-conta de agência a arrancaria da árvore) e o\n`ParentId` de destino (quem recebe — sem isto o tenant iria parar na\nárvore de outro cliente, que passaria a vê-lo em rollup). Todos exigem\npapel EFETIVO `owner`/`admin`, direto ou herdado pela árvore.\n\nCorpo com EXATAMENTE um campo `ParentId`. `null` promove a raiz; campo\nausente é 422 (um `{}` acidental não pode promover uma sub-conta).\nCiclo → 422 (o trigger de closure do core levanta `check_violation`),\nnunca 500. Já estar sob o destino → 200 com `Moved:false`, nada gravado.\n\nO core grava a trilha (`core.tenant_movimentos`, ator = usuário da\nsessão, origem `sessao`) e o evento `platform.tenant.moved`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ParentId"
                ],
                "additionalProperties": false,
                "properties": {
                  "ParentId": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Movido (a closure foi reescrita) ou já estava lá (`Moved:false`).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Tenant": {
                          "$ref": "#/components/schemas/platform_TenantNode"
                        },
                        "Moved": {
                          "type": "boolean"
                        },
                        "PaiAnterior": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`GovernanceRole`/`ReadOnlyRole`: o papel efetivo na conta ou no\ndestino não governa a árvore (só `owner`/`admin`).\n`ParentNotGoverned`: a conta está sob uma conta que o usuário não\ngoverna — só quem governa o pai atual a tira de lá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "Outro movimento mudou o pai da conta entre a leitura e a escrita (`Conflict`); nada foi gravado — recarregue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "treeH.MoveParent",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/quota": {
      "patch": {
        "operationId": "BffSetTenantQuota",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Define ou limpa a quota mensal do tenant.",
        "description": "Corpo com EXATAMENTE um campo `MonthlyQuota`. `null` volta a herdar do\nancestral mais próximo. Campo ausente é 422 — um nome digitado errado\nnão pode limpar a quota em silêncio.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "MonthlyQuota"
                ],
                "additionalProperties": false,
                "properties": {
                  "MonthlyQuota": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quota gravada; `EffectiveQuota` já recalculada.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Tenant": {
                          "$ref": "#/components/schemas/platform_TenantNode"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Papel efetivo na conta não governa a árvore (`GovernanceRole`): só\n`owner`/`admin` (direto ou do ancestral concedente) criam, movem ou\nmudam cota de sub-conta — `developer`/`viewer` não (2026-10-04).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "treeH.SetQuota",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/messages/send": {
      "post": {
        "operationId": "BffSendMessage",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Composer do dashboard — envia em qualquer canal.",
        "description": "Corpo em PascalCase, campo a campo espelhando o contrato interno.\nCampo desconhecido é **400** (`DisallowUnknownFields`), não silêncio.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "Channel",
                  "To"
                ],
                "properties": {
                  "Channel": {
                    "$ref": "#/components/schemas/platform_Channel"
                  },
                  "From": {
                    "type": "string"
                  },
                  "To": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string"
                    }
                  },
                  "Subject": {
                    "type": "string"
                  },
                  "BodyText": {
                    "type": "string"
                  },
                  "BodyHtml": {
                    "type": "string"
                  },
                  "Template": {
                    "type": "string",
                    "description": "Slug do template. Variável declarada e não informada é 422."
                  },
                  "Variables": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "IdempotencyKey": {
                    "type": "string"
                  },
                  "Metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Enfileirado.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Messages": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_Message"
                          }
                        },
                        "ReusedIdempotency": {
                          "type": "boolean"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/platform_RemetenteOuInvalido"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_VerificacaoIndisponivel"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.SendMessage",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/messages": {
      "get": {
        "operationId": "BffListMessages",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Histórico de envios do tenant corrente.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "channel",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/platform_Channel"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/platform_MessageStatus"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_MessagePage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "página de mensagens do tenant, com filtros de canal/status/busca.\n\nOnde é usada: tela de histórico do dashboard.\n\nEntradas: Channel/Status/Q/Cursor/Limit são aceitos em snake_case (`channel`, `status`, `q`, `cursor`, `limit`) e repassados por allowlist — nenhum outro parâmetro atravessa.\n\nSaídas: 200 com `{Messages, NextCursor, HasMore}`; 422 em query malformada.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ListMessages",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/messages/{id}": {
      "get": {
        "operationId": "BffGetMessage",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Detalhe do envio com timeline.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/platform_MessageId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_MessageDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "uma mensagem com corpo, metadata e a trilha de eventos.\n\nOnde é usada: tela de detalhe do envio.\n\nSaídas: 200 com `{Message, Events}`; 404 quando o id não existe NO TENANT — mensagem de outro tenant também é 404, para não confirmar existência (§22.8).",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.GetMessage",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/templates": {
      "get": {
        "operationId": "BffListTemplates",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Templates do tenant.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Templates": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_Template"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "templates do tenant, para a tela de templates e o seletor do composer.\n\nOnde é usada: dashboard.\n\nSaídas: 200 com `{Templates}`.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ListTemplates",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffUpsertTemplate",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Cria ou atualiza um template pelo par (tenant, slug).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "Slug",
                  "Name",
                  "Channel"
                ],
                "properties": {
                  "Slug": {
                    "type": "string"
                  },
                  "Name": {
                    "type": "string"
                  },
                  "Channel": {
                    "$ref": "#/components/schemas/platform_Channel"
                  },
                  "Subject": {
                    "type": "string"
                  },
                  "BodyHtml": {
                    "type": "string"
                  },
                  "BodyText": {
                    "type": "string"
                  },
                  "Variables": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Gravado (upsert — 200 tanto na criação quanto na atualização).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Template": {
                          "$ref": "#/components/schemas/platform_Template"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "gravação idempotente do template pelo par (tenant, slug).\n\nOnde é usada: editor de templates do dashboard.\n\nSaídas: 200 com `{Template}`; 422 em validação do asender_messages.\n\nEfeitos: escreve em messages.templates.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.UpsertTemplate",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/push/devices": {
      "get": {
        "operationId": "BffListDevices",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Devices de push do tenant, para o seletor da tela de disparo.",
        "description": "Fonte do seletor de dispositivos em `/t/{conta}/send/push`. Antes de\nexistir, o caminho respondia 404 e o seletor ficava vazio mesmo com\ndevices no banco — a tela levava o usuário a concluir que a conta não\ntinha dispositivo nenhum.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Página de devices, em PascalCase.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "Devices",
                    "NextCursor",
                    "HasMore"
                  ],
                  "properties": {
                    "Devices": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "NextCursor": {
                      "type": "integer"
                    },
                    "HasMore": {
                      "type": "boolean"
                    },
                    "RequestId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ListDevices",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/push/site-config": {
      "get": {
        "operationId": "BffPushSiteConfig",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Pacote de integração de Web Push para o site do cliente.",
        "description": "Devolve, num JSON só, tudo que o site precisa para receber Web Push:\nchave pública VAPID do tenant, `manifest.json`, o service worker e o\nsnippet de inscrição — os três já preenchidos com os valores desta\ninstalação. Gerar no servidor evita o chamado clássico do integrador que\nesqueceu de substituir um `<SUA_CHAVE_AQUI>`.\n\n**O snippet leva só credencial PÚBLICA (M15, ADR-0057).** A chave de site\n`SiteKey` (`pk_push_…`) é assinada para (tenant da sessão, `origem`s\npedidas) e só é aceita em `POST /v1/push/site/devices`, a partir dessas\norigens. A API key de servidor nunca entra no snippet. Mesmo (tenant,\norigens) gera sempre a mesma chave; trocar de domínio = gerar de novo.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "origem",
            "in": "query",
            "required": true,
            "description": "Origem do site (`https://www.loja.com.br`), repetível, 1 a 10. Sem\ncuringa, sem `null`, sem caminho. Canonizada (minúscula, sem porta\npadrão, sem barra final) e gravada DENTRO da chave de site.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "minItems": 1,
              "maxItems": 10,
              "items": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Artefatos prontos para o site.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "PublicKey",
                    "ApiBase",
                    "SiteKey",
                    "Origens",
                    "Manifest",
                    "ServiceWorker",
                    "Snippet"
                  ],
                  "properties": {
                    "PublicKey": {
                      "type": "string"
                    },
                    "ApiBase": {
                      "type": "string"
                    },
                    "TenantId": {
                      "type": "string"
                    },
                    "SiteKey": {
                      "type": "string",
                      "pattern": "^pk_push_",
                      "description": "Chave PÚBLICA de site; só registra inscrição de Web Push."
                    },
                    "Origens": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "Manifest": {
                      "type": "object"
                    },
                    "ServiceWorker": {
                      "type": "string"
                    },
                    "Snippet": {
                      "type": "string"
                    },
                    "Instructions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "503": {
            "description": "Segredo de assinatura da chave de site ausente."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "siteCfgH.SiteConfig",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/reports/overview": {
      "get": {
        "operationId": "BffReportOverview",
        "x-asender-authz": "session",
        "tags": [
          "bff-reports"
        ],
        "summary": "Funil do período com quebra por canal.",
        "description": "Os três canais SEMPRE aparecem em `ByChannel`, zerados quando não houve\ntráfego. As chaves de `ByChannel` (`email`/`sms`/`push`) ficam em\nminúsculas porque são DADO, não nome de campo.\n\nTaxas em `[0,1]`, arredondadas a 4 casas, grampeadas no teto (a base\npode ter `bounced > sent`, porque bounce não exige `sent_at`).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/platform_From"
          },
          {
            "$ref": "#/components/parameters/platform_To"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Totals": {
                          "$ref": "#/components/schemas/platform_ReportCounters"
                        },
                        "Rates": {
                          "$ref": "#/components/schemas/platform_ReportRates"
                        },
                        "ByChannel": {
                          "type": "object",
                          "propertyNames": {
                            "enum": [
                              "email",
                              "sms",
                              "push"
                            ]
                          },
                          "additionalProperties": {
                            "type": "object",
                            "properties": {
                              "Totals": {
                                "$ref": "#/components/schemas/platform_ReportCounters"
                              },
                              "Rates": {
                                "$ref": "#/components/schemas/platform_ReportRates"
                              }
                            }
                          }
                        },
                        "Period": {
                          "$ref": "#/components/schemas/platform_ReportPeriod"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "reportsBFF.Overview",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/reports/timeseries": {
      "get": {
        "operationId": "BffReportTimeseries",
        "x-asender-authz": "session",
        "tags": [
          "bff-reports"
        ],
        "summary": "Série temporal por bucket e canal.",
        "description": "Um ponto por (bucket, canal), buckets vazios inclusos. O campo continua\nse chamando `Day` mesmo com `interval=hour` — é o início do bucket; a\ngranularidade vem em `Period.Interval`.\n\nEste BFF aceita `hour|day|week|month`, mas o `asender_mkt_api` só\nimplementa `day|hour`: `week`/`month` passam a validação daqui e são\nrecusados com 422 pelo upstream.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/platform_From"
          },
          {
            "$ref": "#/components/parameters/platform_To"
          },
          {
            "name": "channel",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/platform_Channel"
            }
          },
          {
            "name": "interval",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "hour",
                "day",
                "week",
                "month"
              ],
              "default": "day"
            },
            "description": "`week` e `month` são aceitos aqui e recusados pelo upstream (422)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Points": {
                          "type": "array",
                          "items": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/platform_ReportCounters"
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "Day": {
                                    "type": "string",
                                    "format": "date-time"
                                  },
                                  "Channel": {
                                    "$ref": "#/components/schemas/platform_Channel"
                                  }
                                }
                              }
                            ]
                          }
                        },
                        "Period": {
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/platform_ReportPeriod"
                            },
                            {
                              "type": "object",
                              "properties": {
                                "Interval": {
                                  "type": "string"
                                }
                              }
                            }
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "reportsBFF.Timeseries",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/reports/top-templates": {
      "get": {
        "operationId": "BffReportTopTemplates",
        "x-asender-authz": "session",
        "tags": [
          "bff-reports"
        ],
        "summary": "Ranking de templates por volume, com taxas.",
        "description": "`limit` aceito de 1 a 100 nesta borda; o teto REAL aplicado pelo\nrepositório do `asender_mkt_api` é 50.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/platform_From"
          },
          {
            "$ref": "#/components/parameters/platform_To"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Templates": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "Slug": {
                                "type": "string"
                              },
                              "Name": {
                                "type": "string"
                              },
                              "Total": {
                                "type": "integer"
                              },
                              "Sent": {
                                "type": "integer"
                              },
                              "Delivered": {
                                "type": "integer"
                              },
                              "Opened": {
                                "type": "integer"
                              },
                              "Clicked": {
                                "type": "integer"
                              },
                              "DeliveryRate": {
                                "type": "number"
                              },
                              "OpenRate": {
                                "type": "number"
                              },
                              "ClickRate": {
                                "type": "number"
                              }
                            }
                          }
                        },
                        "Period": {
                          "$ref": "#/components/schemas/platform_ReportPeriod"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "reportsBFF.TopTemplates",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/campaigns": {
      "get": {
        "operationId": "BffListCampaigns",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Campanhas do tenant, com status e progresso.",
        "description": "Produto `marketing` (exige entitlement). Tenant resolvido da sessão. A\nquery aceita SÓ `limit` (allowlist) — parâmetro fora da lista é 422\nnomeando-o, nunca listagem sem filtro em silêncio.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK — `{Campaigns:[...]}` (array, nunca null)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "campaignsBFF.ListCampaigns",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateCampaign",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Cria uma campanha em rascunho (draft).",
        "description": "NÃO dispara nada — a campanha nasce em `draft`. O envio real é do worker\n(piso 18), no horário do schedule.\n\n**Remetente obrigatório (2026-10-02):** `FromAddress` vazio numa\ncampanha de e-mail é `422 RemetenteObrigatorio` (antes virava o\nremetente padrão e a campanha saía com todos os lotes recusados). O\nseletor `GET /api/campaigns/remetentes` lista os domínios aceitos.\n\n**Remetente:** `FromAddress` de e-mail só de domínio VERIFICADO da conta\n(SPF+DKIM, no e-mail corporativo). Recusa repassada INTACTA do\nasender_mkt_api: `422 RemetenteNaoVerificado` (mensagem para o usuário,\ncom o domínio) e `503 VerificacaoIndisponivel` — o front mostra aviso com\no atalho para Admin → Domínios.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_CampaignBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criada — `{Campaign}` em draft."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "503": {
            "description": "`VerificacaoIndisponivel` — o domínio do remetente não pôde ser conferido agora; nada foi gravado."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "campaignsBFF.CreateCampaign",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/campaigns/remetentes": {
      "get": {
        "operationId": "BffCampaignSenders",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Domínios verificados que podem assinar o remetente da campanha (seletor do wizard).",
        "description": "Desde 2026-10-02 o remetente da campanha de e-mail é OBRIGATÓRIO e só de\ndomínio verificado (SPF e DKIM). Esta rota alimenta o seletor: os\ndomínios verificados da conta da SESSÃO (nunca de outra conta), lidos no\ne-mail corporativo. Lista vazia é válida — a tela mostra `LinkDominios`\npara verificar um domínio.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Os domínios verificados.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Dominios": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "LinkDominios": {
                          "type": "string",
                          "description": "Atalho para Admin → Domínios da conta."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/platform_VerificacaoIndisponivel"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "campaignsBFF.CampaignSenders",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/campaigns/{id}": {
      "get": {
        "operationId": "BffGetCampaign",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Detalhe de uma campanha, com contadores.",
        "description": "Campanha de outro tenant também é 404 — não confirma existência alheia. Pausada pelo scheduler, traz `PauseReason`: `remetente_nao_verificado` (o domínio do From perdeu a verificação antes do disparo; nada saiu com outro remetente) ou a frase de dependência ausente.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK — `{Campaign}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "campaignsBFF.GetCampaign",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "operationId": "BffUpdateCampaign",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Edita uma campanha (só enquanto em draft ou paused).",
        "description": "A garantia \"só em draft/paused\" é do repo do asender_mkt_api, atômica no WHERE. A pausada é editável para o dono resolver o motivo (ex.: trocar o remetente cujo domínio perdeu a verificação) antes de reagendar.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_CampaignBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — `{Campaign}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "description": "Fora de draft, campo/ref inválido, ou `RemetenteNaoVerificado` (domínio do From não verificado nesta conta)."
          },
          "503": {
            "description": "`VerificacaoIndisponivel` — o domínio do remetente não pôde ser conferido agora; nada foi gravado."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "campaignsBFF.UpdateCampaign",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/campaigns/{id}/schedule": {
      "post": {
        "operationId": "BffScheduleCampaign",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Agenda a campanha (draft/paused→scheduled). NÃO envia.",
        "description": "Piso 18: só marca a intenção — quem entrega no horário é o worker, com o\nguard de efeito externo. A borda valida a FORMA de `ScheduledAt` (RFC\n3339) aqui, sem hop; o asender_mkt_api valida a REGRA (data futura, lista/\ntemplate presentes).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ScheduledAt"
                ],
                "properties": {
                  "ScheduledAt": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-09-10T14:00:00Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — `{Campaign}` em scheduled."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "description": "Data malformada/passada/ausente, campanha fora de draft/paused, ou `RemetenteNaoVerificado` — o domínio do From perdeu a verificação desde o rascunho (fica como está). Reagendar a `paused` é retomá-la: `PauseReason` é limpo e os lotes já aceitos não saem de novo."
          },
          "503": {
            "description": "`VerificacaoIndisponivel` — o domínio do remetente não pôde ser conferido agora."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "campaignsBFF.ScheduleCampaign",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/campaigns/{id}/cancel": {
      "post": {
        "operationId": "BffCancelCampaign",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Cancela a campanha (draft/scheduled/paused→cancelled). Sem corpo.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK — `{Campaign}` em cancelled."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "description": "Status atual não permite cancelamento (ex.: já enviada)."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "move o status para `cancelled`, SÓ a partir de `draft`/`scheduled` (a regra é do repo). Sem corpo.\n\nOnde é usada: botão \"cancelar\" da tela de campanhas.\n\nSaídas: 200 com `{Campaign}`; 404 se não existir; 422 se o status atual não permitir cancelamento (ex.: já enviada).\n\nEfeitos: escreve em messages.campaigns.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "campaignsBFF.CancelCampaign",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/contacts": {
      "get": {
        "operationId": "BffListContacts",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Audiência do tenant.",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "list_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filtra contatos que têm esta tag."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "$ref": "#/components/schemas/platform_CursorPage"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Contacts": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_Contact"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ListContacts",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateContact",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Cria um contato.",
        "description": "`Subscribed` ausente vira `true` — opt-in é o default do cadastro manual.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_ContactInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criado.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Contact": {
                          "$ref": "#/components/schemas/platform_Contact"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.CreateContact",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/contacts/bulk": {
      "post": {
        "operationId": "BffBulkContacts",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Importa até 5000 contatos, com upsert pela identidade natural.",
        "description": "Import parcial é resultado legítimo: itens ruins voltam em `Errors` com\no índice de origem, e o lote não é invalidado por causa deles. Lote\nvazio ou acima do teto é 422.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "Contacts"
                ],
                "properties": {
                  "Contacts": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 5000,
                    "items": {
                      "$ref": "#/components/schemas/platform_ContactInput"
                    }
                  },
                  "ListId": {
                    "type": "string",
                    "description": "Opcional: anexa os contatos importados a esta lista."
                  },
                  "Tags": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "string"
                    },
                    "description": "Opcional: aplica estas tags aos contatos importados."
                  },
                  "ImportId": {
                    "type": "string",
                    "description": "Opcional: id da importação, para correlação."
                  },
                  "ConsentProof": {
                    "type": "object",
                    "additionalProperties": false,
                    "description": "Opcional: prova de consentimento da importação.",
                    "properties": {
                      "BaseLegal": {
                        "type": "string"
                      },
                      "Texto": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lote processado.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Created": {
                          "type": "integer"
                        },
                        "Updated": {
                          "type": "integer"
                        },
                        "Errors": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "Index": {
                                "type": "integer"
                              },
                              "Email": {
                                "type": "string"
                              },
                              "Phone": {
                                "type": "string"
                              },
                              "Error": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "Contacts": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_Contact"
                          }
                        },
                        "Suppressed": {
                          "type": "integer",
                          "description": "Contatos suprimidos (descadastro/bounce) no lote."
                        },
                        "AddedToList": {
                          "type": "integer"
                        },
                        "Tagged": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.BulkContacts",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/contacts/{id}": {
      "get": {
        "operationId": "BffGetContact",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "A ficha de UM contato da audiência, com o consentimento por canal.",
        "description": "Repassa `GET /v1/contacts/{id}` do mkt_api (produto `marketing`). Contato de outro tenant → o mesmo 404 de inexistente.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Contact": {
                          "$ref": "#/components/schemas/platform_Contact"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.GetContact",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "tags": [
          "api"
        ],
        "summary": "altera parcialmente um contato do tenant da SESSÃO — hoje o caminho do editor de consentimento por canal.",
        "description": "altera parcialmente um contato do tenant da SESSÃO — hoje o caminho do editor de consentimento por canal.\n\nOnde é usada: tela de audiência do hub, ao revogar ou restaurar um canal.\n\nSaídas: 200 com `{Contact}`; 404 quando o contato não é do tenant; 422 em validação (canal fora dos quatro, corpo vazio).\n\nEfeitos: escreve em messages.contacts e no consentimento por canal. # O tenant vem da SESSÃO, e o id vai na ROTA `resolveSessionTenant` resolve a conta do token verificado; o corpo nunca escolhe em quem se escreve (piso 3). É o que faz `PATCH /api/contacts/<id do vizinho>` ser 404 em vez de edição alheia — a checagem é do `asender_mkt_api`, com o tenant que ESTE handler passou. # Por que `map[string]any` e não uma struct PATCH é PARCIAL. Uma struct com campos zero-value não distingue \"não mandou\" de \"mandou vazio\", e aqui a diferença decide se o consentimento de um canal muda ou fica como está. Repassar o corpo cru deixa a decisão com quem tem a regra — o `asender_mkt_api` — em vez de duplicá-la no BFF, que é como as duas cópias do filtro de descadastro divergiram antes (ver o contrato da audiência).",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.PatchContact",
        "x-asender-fonte": "codigo"
      },
      "delete": {
        "operationId": "BffDeleteContact",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Remove o contato do tenant.",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404). A semântica de exclusão é do mkt_api; contato de outro tenant responde 404.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.DeleteContact",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/lists": {
      "post": {
        "operationId": "BffCreateList",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Cria uma lista de contatos.",
        "description": "O upstream faz upsert por slug; este BFF responde sempre 201. Exige `marketing` OU `emailmarketing` (a LEITURA em `GET /api/lists` aceita também `sns`/`zapify`/`push`; a escrita, não).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "Name"
                ],
                "properties": {
                  "Name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "Slug": {
                    "type": "string"
                  },
                  "Description": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criada (ou atualizada pelo slug).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "List": {
                          "$ref": "#/components/schemas/platform_ContactList"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.CreateList",
        "x-asender-fonte": "spec+codigo"
      },
      "get": {
        "operationId": "BffListLists",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Listas de contatos com contagem de membros.",
        "description": "**Quem pode ler (S128):** quem acessa o produto `marketing` OU qualquer\numa das ferramentas de despacho — `emailmarketing`, `sns`, `zapify`,\n`push` —, decidido pelo PDP do asender_auth_api (`/v1/authz/decide`),\nproduto a produto nesta ordem; a primeira permissão basta. É a fonte do\nseletor de público das telas de e-mail, SMS, WhatsApp e push.\nNenhum permitido, conta suspensa ou PDP fora sem decisão em cache ⇒\n`404` (deny-default; 404 e não 403 para não virar oráculo).\nSó a LEITURA vale para `sns`/`zapify`/`push`. O resto da audiência\n(criar lista, membros, contatos, tags) exige `marketing` OU\n`emailmarketing`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Lists": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_ContactList"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ListLists",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/lists/{id}/members": {
      "post": {
        "operationId": "BffAddListMembers",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Anexa contatos a uma lista.",
        "description": "`NotFound` traz os ids que não existem no tenant — o servidor diz o que\nNÃO pôde fazer, em vez de descartar em silêncio. Idempotente.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public id da lista (`lst_<hex>`)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "ContactIds"
                ],
                "properties": {
                  "ContactIds": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Added": {
                          "type": "integer"
                        },
                        "NotFound": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.AddListMembers",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "BffRemoveListMembers",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Tira contatos de uma lista (DELETE com corpo; até 500 ids).",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404). `NotFound` traz os ids que não pertencem à lista no tenant.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public id da lista (`lst_<hex>`)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "ContactIds"
                ],
                "properties": {
                  "ContactIds": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Removed": {
                          "type": "integer"
                        },
                        "NotFound": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.RemoveListMembers",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/lists/{id}/contacts": {
      "get": {
        "operationId": "BffListListContacts",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Membros de uma lista, paginados por cursor.",
        "description": "Repassa a paginação do serviço de mensageria sem cortar nada: enquanto\n`HasMore` for true, chame de novo com `cursor=NextCursor` até a última\npágina. `limit` fora de 1..100 é 422 (nunca reduzido em silêncio).\nSubstitui o uso de `GET /api/contacts?list_id=` com `limit=500`, que\nperdia os membros além do 500º. Lista de outro tenant responde 404.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public id da lista (`lst_<hex>`)."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "`NextCursor` da página anterior; omita na primeira."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Contacts": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_Contact"
                          }
                        },
                        "NextCursor": {
                          "type": "integer",
                          "description": "0 na última página."
                        },
                        "HasMore": {
                          "type": "boolean"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ListListContacts",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tags": {
      "get": {
        "operationId": "BffListTags",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Tags do tenant com a contagem de contatos.",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Tags": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "Name": {
                                "type": "string"
                              },
                              "ContactCount": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ListTags",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/contacts/tags": {
      "post": {
        "operationId": "BffApplyContactTags",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Aplica tags a contatos (até 500 ids e 10 tags).",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404). `NotFound` traz os ids que não existem no tenant.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_ContactTagsBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Applied": {
                          "type": "integer"
                        },
                        "NotFound": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ContactTags",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "BffRemoveContactTags",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Remove tags de contatos (DELETE com corpo; até 500 ids e 10 tags).",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/platform_ContactTagsBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Removed": {
                          "type": "integer"
                        },
                        "NotFound": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ContactTags",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/contacts/{id}/lists": {
      "get": {
        "operationId": "BffContactLists",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Listas a que o contato pertence.",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Lists": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/platform_ContactList"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ContactLists",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/contacts/{id}/consent-proofs": {
      "get": {
        "operationId": "BffContactConsentProofs",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Provas de consentimento do contato.",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Proofs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "Source": {
                                "type": "string"
                              },
                              "FormId": {
                                "type": "string"
                              },
                              "Texto": {
                                "type": "string"
                              },
                              "BaseLegal": {
                                "type": "string"
                              },
                              "ConcedidoEm": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.ContactConsentProofs",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/lists/{id}": {
      "delete": {
        "operationId": "BffDeleteList",
        "x-asender-authz": "session",
        "tags": [
          "bff-messaging"
        ],
        "summary": "Remove a lista (os contatos permanecem).",
        "description": "Exige `marketing` OU `emailmarketing` (PDP, nesta ordem; negado => 404).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removida."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/platform_NoTenant"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "msgBFF.DeleteList",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/api-keys": {
      "get": {
        "operationId": "BffListApiKeys",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Lista as API keys da conta.",
        "description": "O `{id}` vem do cliente e é autorizado contra a pertinência do usuário\n(`authorizeTenantParam`) antes de qualquer hop; conta alheia responde\n404, nunca 403. Espelha `/v1/tenants/{id}/api-keys` do core — o dashboard\nnão fala `/v1` direto porque o segredo de serviço não vive no front.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK (corpo repassado do asender_plataforma_api)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "apiKeysH.ListKeys",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateApiKey",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Emite uma API key na conta (o texto puro volta UMA vez).",
        "description": "O `user_id` vem SEMPRE da sessão, nunca do corpo. A chave em texto puro é\ndevolvida uma única vez, no corpo do core; não é persistida nem logada.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "scopes"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "mode": {
                    "type": "string",
                    "description": "Ex.: live | test (repassado ao core)."
                  },
                  "scopes": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "enum": [
                        "email:send",
                        "email:read",
                        "sms:send",
                        "push:send",
                        "push:devices",
                        "whatsapp:send",
                        "telegram:send",
                        "whatsweb:send",
                        "telegramconta:send",
                        "account:read",
                        "logs:read",
                        "mail:admin",
                        "forms:read",
                        "forms:write",
                        "pages:read",
                        "pages:write",
                        "pages:publish",
                        "leads:read",
                        "conversions:read",
                        "analytics:read",
                        "campaigns:read",
                        "messaging:read",
                        "conversations:read",
                        "journeys:read",
                        "webhooks:manage",
                        "dns:read",
                        "dns:write",
                        "leads:write"
                      ]
                    },
                    "description": "Escopos da chave (OBRIGATÓRIO; só valores do catálogo). Vazio, ausente ou valor desconhecido (`full_access`, `send_only`, `emails:*`…) → 400 com a lista válida."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Chave criada (corpo do core",
            "com o segredo UMA vez).": null
          },
          "400": {
            "description": "`ValidationError`: `scopes` ausente, vazio ou com valor fora do catálogo; a mensagem lista os escopos válidos."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "description": "name ausente."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "apiKeysH.CreateKey",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/api-keys/{keyId}": {
      "delete": {
        "operationId": "BffRevokeApiKey",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Revoga uma API key da conta.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Revogada."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "Onde é usada: DELETE /api/tenants/{id}/api-keys/{keyId}.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "apiKeysH.RevokeKey",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/channels/whatsapp/credentials": {
      "get": {
        "operationId": "BffGetWhatsAppCredentials",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Credencial de WhatsApp da conta, MASCARADA.",
        "description": "O `{id}` vem do cliente e é autorizado por pertinência antes de qualquer\nhop (`authorizeTenantParam`); conta alheia responde **404**, nunca 403.\n\nO `access_token` NUNCA volta em claro: o core devolve mascarado.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK (corpo repassado do core, com o token mascarado)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "description": "Id de conta vazio no caminho."
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappH.Get",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "operationId": "BffPutWhatsAppCredentials",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Cadastra/substitui a credencial de WhatsApp da conta.",
        "description": "Exige papel de ESCRITA na conta (`authorizeTenantParam` recusa `viewer`\ncom 403 `ReadOnlyRole`); conta alheia continua respondendo 404.\n\nCorpo ESTRITO: campo desconhecido ou JSON malformado é recusado aqui com\n400. O core cifra o `access_token`; o eco da resposta vem mascarado.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone_id",
                  "access_token"
                ],
                "properties": {
                  "phone_id": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Phone number ID da Meta Cloud API."
                  },
                  "access_token": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Cifrado no core; nunca retornado em claro nem logado."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK (corpo repassado do core, com o token mascarado)."
          },
          "400": {
            "description": "Corpo não é JSON válido ou traz campo desconhecido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Papel de leitura (`viewer`) não altera a conta."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "description": "phone_id ou access_token ausente/vazio."
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappH.Put",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/channels/whatsapp/numbers": {
      "get": {
        "operationId": "BffListWhatsAppNumeros",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Números de WhatsApp da conta (vários por conta; o padrão primeiro).",
        "description": "Espelho de sessão de `/v1/tenants/{id}/channels/whatsapp/numbers` do core.\nO `{id}` é autorizado por pertinência antes de qualquer hop; conta alheia\nresponde **404**. O token só aparece mascarado.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK — `{numbers: [...]}` repassado do core."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappNumerosH.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateWhatsAppNumero",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Adiciona um número de WhatsApp à conta.",
        "description": "Papel de ESCRITA. Corpo ESTRITO. Sem `access_token`, o core reaproveita o\ntoken de outro número da mesma WABA na conta. 409 `NumeroEmUso` quando o\nnúmero já está em alguma conta (nunca diz qual).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "phone_id"
                ],
                "properties": {
                  "phone_id": {
                    "type": "string",
                    "minLength": 1
                  },
                  "waba_id": {
                    "type": "string"
                  },
                  "display_name": {
                    "type": "string"
                  },
                  "display_number": {
                    "type": "string"
                  },
                  "access_token": {
                    "type": "string",
                    "description": "Cifrado no core; nunca retornado em claro."
                  },
                  "default": {
                    "type": "boolean"
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criado (eco do core, token mascarado)."
          },
          "400": {
            "description": "Corpo inválido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Papel de leitura."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`NumeroEmUso`."
          },
          "422": {
            "description": "phone_id ausente."
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappNumerosH.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/channels/whatsapp/numbers/{numero}": {
      "patch": {
        "operationId": "BffUpdateWhatsAppNumero",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Edita um número (nome, WABA, token, ativo).",
        "description": "Parcial (campo ausente não muda). `apply_token_to_waba` leva o token novo\naos irmãos da WABA. Pausar o padrão com outro ativo é 409 `PadraoComOutros`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "waba_id": {
                    "type": "string"
                  },
                  "display_name": {
                    "type": "string"
                  },
                  "display_number": {
                    "type": "string"
                  },
                  "access_token": {
                    "type": "string"
                  },
                  "apply_token_to_waba": {
                    "type": "boolean"
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK (eco do core, token mascarado)."
          },
          "400": {
            "description": "Corpo inválido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`PadraoComOutros`."
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappNumerosH.Editar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "BffDeleteWhatsAppNumero",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Remove um número — pede confirmação se houver conversas abertas.",
        "description": "Sem `confirm=true`, o BFF pergunta ao atendimento (com o Bearer da\nsessão) quantas conversas abertas entraram pelo número. Havendo alguma —\nou não dando para saber — responde **409 `ConversasAbertas`** com\n`open_conversations` (null = desconhecido) e NÃO remove. O histórico das\nconversas nunca é apagado; só as respostas por este número param.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "confirm",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`ConversasAbertas` (confirme) ou `PadraoComOutros`."
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappNumerosH.Remover",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/channels/whatsapp/numbers/{numero}/default": {
      "post": {
        "operationId": "BffSetDefaultWhatsAppNumero",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Define o número como remetente padrão.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "numero",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK (o novo padrão)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`NumeroInativo`."
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappNumerosH.DefinirPadrao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/channels/whatsapp/numbers/{numero}/test": {
      "post": {
        "operationId": "BffTestWhatsAppNumero",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Testar conexão do número com a Meta (simulado fora de produção).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "numero",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ok, simulated, display_number?, verified_name?, detail?}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "503": {
            "description": "Validação indisponível / Graph API fora."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsappNumerosH.Testar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/members": {
      "get": {
        "operationId": "BffListMembers",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Membros da conta.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "quem tem acesso à conta.\n\nOnde é usada: tela de equipe.\n\nEfeitos: uma leitura no core.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "membersH.ListMembers",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffAddMember",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Vincula um usuário à conta.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "dá acesso direto a um usuário que já existe.\n\nOnde é usada: tela de equipe.\n\nEfeitos: uma escrita no core.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "membersH.AddMember",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/members/{userId}": {
      "delete": {
        "operationId": "BffRemoveMember",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Desvincula um usuário da conta.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "tira o acesso de alguém.\n\nOnde é usada: tela de equipe.\n\nEfeitos: uma escrita no core.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "membersH.RemoveMember",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/invitations": {
      "get": {
        "operationId": "BffListInvitations",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Convites pendentes da conta.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "membersH.ListInvitations",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateInvitation",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Convida alguém para a conta.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "convida um e-mail para a conta.\n\nOnde é usada: tela de equipe.\n\nEfeitos: uma escrita no core (e, quando houver envio, um e-mail).",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "membersH.CreateInvitation",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/invitations/{token}": {
      "delete": {
        "operationId": "BffRevokeInvitation",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Cancela um convite pendente.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "cancela um convite que ainda não foi aceito.\n\nOnde é usada: tela de equipe.\n\nEfeitos: uma escrita no core.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "membersH.RevokeInvitation",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/invitations/accept": {
      "post": {
        "operationId": "BffAcceptInvitation",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Aceita um convite e vincula o usuário à conta.",
        "description": "O usuário vem da sessão; quem autoriza é o token do convite (validado no\ncore). A rota não exige pertinência à conta — quem aceita ainda não é\nmembro. O status e o corpo do core são repassados.\n\n**Herança de acessos**: com o aceite em `200`, o BFF pede ao\n`asender_auth_api` (`POST /v1/tenants/{id}/acessos/herdar`) que dono e\nadmin da conta herdem o maior papel de cada ferramenta assinada;\ndeveloper/viewer não herdam. Falha na herança **não** desfaz o aceite\n(continua `200`): vai para o log com o request id, e o acesso pode ser\nconcedido na tela Equipe ou religando a ferramenta na Assinatura.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "credencial ausente ou inválida"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "membersH.AcceptInvitation",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/importaveis": {
      "get": {
        "operationId": "BffListImportableTenants",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "As outras contas que o usuário governa e pode importar para a árvore de {id}.",
        "description": "Contas de que o usuário é membro DIRETO com papel `owner`/`admin`, fora\nda árvore de `{id}` (ela, a descendência e os ancestrais — já estão nela\nou seria ciclo). Cada uma com o pai atual (`PaiId`/`PaiNome`) e, quando o\nusuário não governa esse pai, `Bloqueio: ParentNotGoverned` (a tela\nmostra desabilitada e explica). Exige governar `{id}`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "A lista, em ordem de nome.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Contas": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "Id",
                              "Name",
                              "Papel"
                            ],
                            "properties": {
                              "Id": {
                                "type": "string"
                              },
                              "Name": {
                                "type": "string"
                              },
                              "Slug": {
                                "type": "string"
                              },
                              "Papel": {
                                "type": "string",
                                "enum": [
                                  "owner",
                                  "admin"
                                ]
                              },
                              "PaiId": {
                                "type": "string"
                              },
                              "PaiNome": {
                                "type": "string"
                              },
                              "Bloqueio": {
                                "type": "string",
                                "enum": [
                                  "ParentNotGoverned"
                                ]
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`GovernanceRole`: o usuário não governa `{id}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "arvore.ContasImportaveis",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/movimento/previa": {
      "get": {
        "operationId": "BffPreviewTenantMove",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Prévia de mover/importar uma conta, sem escrever nada.",
        "description": "`?ParentId=acc_x` (ausente = promover a raiz). A MESMA autorização dos\ntrês lados do PATCH — a prévia não é oráculo sobre conta alheia.\nDevolve, do core: a conta, o pai atual, o destino, `NoOp` (já está lá),\n`Ciclo`, `ContasMovidas` e a cota efetiva antes/depois. Usada pelo\npasso de confirmação de \"Importar conta existente\" (admin).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "ParentId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A prévia.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "Preview": {
                          "type": "object",
                          "properties": {
                            "Tenant": {
                              "$ref": "#/components/schemas/platform_TenantNode"
                            },
                            "PaiAtual": {
                              "oneOf": [
                                {
                                  "$ref": "#/components/schemas/platform_TenantNode"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "Destino": {
                              "oneOf": [
                                {
                                  "$ref": "#/components/schemas/platform_TenantNode"
                                },
                                {
                                  "type": "null"
                                }
                              ]
                            },
                            "NoOp": {
                              "type": "boolean"
                            },
                            "Ciclo": {
                              "type": "boolean"
                            },
                            "ContasMovidas": {
                              "type": "integer"
                            },
                            "QuotaPropria": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "QuotaEfetivaAntes": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "QuotaEfetivaDepois": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`GovernanceRole` ou `ParentNotGoverned` (ver o PATCH /parent).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "arvore.PreviaDoMovimento",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/atividade": {
      "get": {
        "operationId": "TenantListarAtividade",
        "x-asender-authz": "session",
        "tags": [
          "bff-plataforma"
        ],
        "summary": "A trilha de ATIVIDADE da própria conta (admin/owner), ADR-0114.",
        "description": "Mesma leitura de `/api/admin/atividade`, mas o `tenant_id` enviado aos serviços é SEMPRE o `{id}`\nautorizado pela sessão; `conta`/`tenant_id` na query são ignorados. Conta alheia 404; papel sem\ngovernança (viewer/developer) 403.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ator",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          },
          {
            "name": "servico",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 32
            }
          },
          {
            "name": "recurso_tipo",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          },
          {
            "name": "recurso_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          },
          {
            "name": "acao",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[a-z][a-z0-9_]{1,39}$"
            }
          },
          {
            "name": "de",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC3339 ou AAAA-MM-DD."
          },
          {
            "name": "ate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC3339 ou AAAA-MM-DD (dia inclusivo)."
          },
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "formato",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "csv"
              ]
            },
            "description": "csv exporta (anexo; células que começam com = + - @ ganham aspa simples)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "itens": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "`{id, servico, tenant_id, ator, origem, acao, recurso_tipo, recurso_id, antes, depois, request_id, ip, em}` (ADR-0114; segredos e PII já redigidos)."
                      }
                    },
                    "indisponiveis": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "`{servico, motivo: timeout|erro}`."
                      }
                    },
                    "proximo_cursor": {
                      "type": "string",
                      "description": "Cursor composto opaco; vazio = fim."
                    },
                    "servicos": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "RequestId": {
                      "type": "string"
                    }
                  }
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "filtro ou cursor inválido"
          },
          "403": {
            "description": "papel sem governança da conta"
          },
          "404": {
            "description": "conta alheia"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ListarConta",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/busca": {
      "get": {
        "operationId": "BffBuscaFederada",
        "x-asender-authz": "session",
        "tags": [
          "bff-resumo"
        ],
        "summary": "Busca federada do hub: um termo procurado em todas as ferramentas da pessoa na conta.",
        "description": "O BFF pergunta ao PDP (`/v1/authz/eu`) quais ferramentas a pessoa acessa na conta (resolvida por pertinência; conta alheia → 404) e chama, em PARALELO e com prazo curto por ferramenta (`BUSCA_PRAZO`, default 1,5 s), as buscas que cada uma já faz — sempre com a credencial da PRÓPRIA pessoa: o Bearer da sessão (crm: `/v1/contatos?q=`, `/v1/empresas?q=`; pages: `/v1/paginas` filtrada, `/v1/links?busca=`; sns, zapify, push e emailmarketing: `GET /v1/busca?q=&limit=` → `{templates, campaigns}`; ferramentas novas: o endpoint comum `GET /v1/busca?q=&limite=` → `{itens:[…]}`) ou o caminho de serviço com o usuário verificado (mail: `/mailboxes/minhas` + `/mailboxes/{id}/messages?todas=1&q=`, até 3 caixas, com o PEP e as concessões do usuário no mail_api; marketing: `/v1/contacts?q=` e `/v1/campaigns` filtradas, o mesmo caminho de `/api/contacts`). Ferramenta lenta ou fora vem como `{indisponivel: true}` e o resto responde (piso 10); ferramenta que RECUSA (403/404) some; ferramenta sem resultado não aparece. Até 5 itens por fonte e 10 por ferramenta. `caminho` é sempre relativo à ferramenta, com o marcador `{tenant}` (o BFF descarta o resto).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantHeader"
          },
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "q",
                    "grupos"
                  ],
                  "properties": {
                    "q": {
                      "type": "string"
                    },
                    "grupos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "ferramenta",
                          "nome",
                          "subdominio",
                          "itens"
                        ],
                        "properties": {
                          "ferramenta": {
                            "type": "string",
                            "examples": [
                              "crm"
                            ]
                          },
                          "nome": {
                            "type": "string",
                            "examples": [
                              "CRM"
                            ]
                          },
                          "subdominio": {
                            "type": "string",
                            "examples": [
                              "crm"
                            ]
                          },
                          "indisponivel": {
                            "type": "boolean"
                          },
                          "itens": {
                            "type": "array",
                            "maxItems": 10,
                            "items": {
                              "type": "object",
                              "required": [
                                "tipo",
                                "titulo",
                                "caminho"
                              ],
                              "properties": {
                                "tipo": {
                                  "type": "string",
                                  "examples": [
                                    "Contatos"
                                  ]
                                },
                                "titulo": {
                                  "type": "string",
                                  "maxLength": 120
                                },
                                "subtitulo": {
                                  "type": "string",
                                  "maxLength": 120
                                },
                                "caminho": {
                                  "type": "string",
                                  "examples": [
                                    "/t/{tenant}/crm/contatos/ct_1"
                                  ]
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`ValidationError` — q com menos de 2 ou mais de 100 caracteres, ou com caractere de controle."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`NoTenant` — o usuário ainda não pertence a conta nenhuma."
          },
          "404": {
            "description": "Conta alheia no `X-Asender-Tenant` (mesma resposta de inexistente)."
          },
          "429": {
            "description": "`Throttling` — teto por principal."
          },
          "502": {
            "description": "core ou PDP (auth) indisponível — sem eles não há como saber o que a pessoa vê."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "busca.Listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/telegram/send": {
      "post": {
        "operationId": "PublicSendTelegram",
        "x-asender-authz": "apikey",
        "tags": [
          "public-telegram"
        ],
        "summary": "Envia uma mensagem de texto por Telegram, pelo bot da conta.",
        "description": "`202` quer dizer **enfileirado**, não entregue. A mensagem sai pelo bot\nque a conta conectou no Telegram; `BotId` (o id público `tgb_...`, nunca\no token do bot) só é necessário quando a conta tem mais de um bot.\n\n**O destinatário precisa ter iniciado o bot.** O Telegram só deixa o bot\nescrever para quem o abriu e enviou `/start`. Para um `ChatId` que nunca\nfez isso a resposta é `422 ChatNaoIniciado` e nada é enviado: peça ao\ndestinatário que abra o bot e envie `/start`. Quem bloqueou o bot depois\ntambém deixa de receber.\n\n`ChatId` é o id numérico do chat (positivo para pessoas, negativo para\ngrupos), como texto ou como inteiro. Use `Idempotency-Key` para repetir o\npedido com segurança: a mesma chave com o mesmo corpo não duplica a\nmensagem; com outro corpo devolve `409`.\n\nChave de servidor **nunca** vai para o navegador: pedido com `Origin` /\n`Sec-Fetch-*` leva `403 ServerKeyInBrowser`. Com o canal fora do ar a\nresposta é `503 ServiceUnavailable` com `Retry-After`; repita depois.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ChatId",
                  "Text"
                ],
                "properties": {
                  "ChatId": {
                    "oneOf": [
                      {
                        "type": "string",
                        "pattern": "^-?[0-9]{1,19}$"
                      },
                      {
                        "type": "integer",
                        "format": "int64"
                      }
                    ],
                    "description": "Id numérico do chat no Telegram (negativo em grupos)."
                  },
                  "Text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4096,
                    "description": "Texto da mensagem (até 4096 caracteres)."
                  },
                  "BotId": {
                    "type": "string",
                    "pattern": "^tgb_[A-Za-z0-9]{1,64}$",
                    "description": "Id público do bot. Opcional se a conta tem um só bot."
                  }
                }
              },
              "examples": {
                "basico": {
                  "summary": "Mensagem para um chat que já deu /start",
                  "value": {
                    "ChatId": "123456789",
                    "Text": "Olá! Seu pedido saiu para entrega."
                  }
                },
                "comBot": {
                  "summary": "Conta com mais de um bot",
                  "value": {
                    "ChatId": 123456789,
                    "Text": "Seu código de retirada é 4821.",
                    "BotId": "tgb_k3x9a2"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Aceito e enfileirado; a entrega é assíncrona.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "MessageId": {
                          "type": "string"
                        },
                        "Status": {
                          "type": "string",
                          "example": "queued"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "MessageId": "tgm_01k9c3p7d2",
                  "Status": "queued",
                  "RequestId": "req_7a41"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`InsufficientScope` (a chave não tem `telegram:send`), `TenantSuspended` ou `ServerKeyInBrowser`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "`NotFound`: o `BotId` não existe nesta conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/platform_IdempotencyConflict"
          },
          "422": {
            "description": "`ChatNaoIniciado`: esse chat nunca enviou `/start` ao bot (ou `ValidationError` na chave `Idempotency-Key`). Nada é enviado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                },
                "example": {
                  "Error": {
                    "Type": "Sender",
                    "Code": "ChatNaoIniciado",
                    "Message": "This chat has not started the bot yet. The recipient must open the bot in Telegram and send /start before it can receive messages."
                  },
                  "RequestId": "req_7a42"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "`ServiceUnavailable`: o canal de Telegram está fora do ar ou não está configurado. Traz `Retry-After`; repita depois.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "telegram.Send",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/whatsweb/send": {
      "post": {
        "operationId": "PublicSendWhatsWeb",
        "x-asender-authz": "apikey",
        "tags": [
          "public-whatsweb"
        ],
        "summary": "Envia uma mensagem de texto pelo WhatsApp da conta, via sessão do WhatsApp Web (canal não oficial).",
        "description": "**Risco do canal não oficial:** this channel uses an unofficial WhatsApp Web\nsession; the number may be banned by WhatsApp. Use apenas com um número\nque a conta aceita arriscar, e prefira `POST /v1/whatsapp/send` (canal\noficial) sempre que possível.\n\n`202` quer dizer **enfileirado**, não entregue. A mensagem sai pelo número\npareado pela conta; `SessionId` (o id público `wws_...`) só é necessário\nquando a conta tem mais de uma sessão.\n\n**Só responde a conversas que já existem.** O canal escreve para quem já\nfalou com este número. Para um número que nunca escreveu a resposta é\n`422 ChatInexistente` e nada é enviado, a menos que o gestor da conta\nhabilite novos contatos por dia. `To` é o telefone em E.164 (`+5511987654321`).\n\nUse `Idempotency-Key` para repetir o pedido com segurança: a mesma chave\ncom o mesmo corpo não duplica a mensagem; com outro corpo devolve `409`.\nChave de servidor **nunca** vai para o navegador: pedido com `Origin` /\n`Sec-Fetch-*` leva `403 ServerKeyInBrowser`.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "To",
                  "Text"
                ],
                "properties": {
                  "To": {
                    "type": "string",
                    "pattern": "^\\+[1-9][0-9]{7,14}$",
                    "description": "Telefone do destinatário em E.164."
                  },
                  "Text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4096,
                    "description": "Texto da mensagem (até 4096 caracteres)."
                  },
                  "SessionId": {
                    "type": "string",
                    "pattern": "^wws_[A-Za-z0-9]{1,64}$",
                    "description": "Id público da sessão. Opcional se a conta tem uma só."
                  }
                }
              },
              "examples": {
                "basico": {
                  "summary": "Resposta a um cliente que já escreveu",
                  "value": {
                    "To": "+5511987654321",
                    "Text": "Olá! Seu pedido saiu para entrega."
                  }
                },
                "comSessao": {
                  "summary": "Conta com mais de uma sessão",
                  "value": {
                    "To": "+5511987654321",
                    "Text": "Seu código de retirada é 4821.",
                    "SessionId": "wws_k3x9a2"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Aceito e enfileirado; a entrega é assíncrona.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "MessageId": {
                          "type": "string"
                        },
                        "Status": {
                          "type": "string",
                          "example": "queued"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "MessageId": "wwm_01k9c3p7d2",
                  "Status": "queued",
                  "RequestId": "req_7a41"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`InsufficientScope` (a chave não tem `whatsweb:send`), `TenantSuspended` ou `ServerKeyInBrowser`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "`NotFound`: o `SessionId` não existe nesta conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/platform_IdempotencyConflict"
          },
          "422": {
            "description": "`ChatInexistente`: esse número nunca escreveu para este WhatsApp. Nada é enviado. Também `ValidationError` na chave `Idempotency-Key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                },
                "example": {
                  "Error": {
                    "Type": "Sender",
                    "Code": "ChatInexistente",
                    "Message": "This number has never messaged this WhatsApp; the unofficial channel only replies to existing chats unless new contacts per day are enabled by the account manager."
                  },
                  "RequestId": "req_7a42"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "`ServiceUnavailable`: o canal está fora do ar ou não está configurado. Traz `Retry-After`; repita depois.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "whatsWeb.Send",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/telegramconta/send": {
      "post": {
        "operationId": "PublicSendTelegramConta",
        "x-asender-authz": "apikey",
        "tags": [
          "public-telegramconta"
        ],
        "summary": "Envia uma mensagem de texto pela conta do Telegram (não por bot).",
        "description": "`202` quer dizer **enfileirado**, não entregue. A mensagem sai pela conta\nde atendimento que a conta Asender conectou no Telegram; `SessionId` (o\nid público `tgc_...`) só é necessário quando há mais de uma conta conectada.\n\n`To` aceita `@username`, telefone ou id de peer. A conta só escreve para\ndiálogos que já existem: para um destino sem conversa a resposta é\n`422 DialogoInexistente` e nada é enviado. Envio em massa pode levar o\nTelegram a restringir a conta; use com moderação.\n\nUse `Idempotency-Key` para repetir o pedido com segurança: a mesma chave\ncom o mesmo corpo não duplica a mensagem; com outro corpo devolve `409`.\nChave de servidor **nunca** vai para o navegador: pedido com `Origin` /\n`Sec-Fetch-*` leva `403 ServerKeyInBrowser`.",
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "To",
                  "Text"
                ],
                "properties": {
                  "To": {
                    "type": "string",
                    "description": "Username (`@maria_silva`), telefone (`+5511987654321`) ou id de peer do destinatário."
                  },
                  "Text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4096,
                    "description": "Texto da mensagem (até 4096 caracteres)."
                  },
                  "SessionId": {
                    "type": "string",
                    "pattern": "^tgc_[A-Za-z0-9]{1,64}$",
                    "description": "Id público da conta conectada. Opcional se há uma só."
                  }
                }
              },
              "examples": {
                "porUsername": {
                  "summary": "Mensagem por username",
                  "value": {
                    "To": "@maria_silva",
                    "Text": "Olá! Seu pedido saiu para entrega."
                  }
                },
                "porTelefone": {
                  "summary": "Mensagem por telefone, com conta escolhida",
                  "value": {
                    "To": "+5511987654321",
                    "Text": "Seu código de retirada é 4821.",
                    "SessionId": "tgc_k3x9a2"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Aceito e enfileirado; a entrega é assíncrona.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "MessageId": {
                          "type": "string"
                        },
                        "Status": {
                          "type": "string",
                          "example": "queued"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "MessageId": "tgcm_01k9c3p7d2",
                  "Status": "queued",
                  "RequestId": "req_7a41"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_ValidationDetails"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`InsufficientScope` (a chave não tem `telegramconta:send`), `TenantSuspended` ou `ServerKeyInBrowser`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "`NotFound`: o `SessionId` não existe nesta conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/platform_IdempotencyConflict"
          },
          "422": {
            "description": "`DialogoInexistente`: a conta não tem conversa com esse destino. Nada é enviado. Também `ValidationError` na chave `Idempotency-Key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                },
                "example": {
                  "Error": {
                    "Type": "Sender",
                    "Code": "DialogoInexistente",
                    "Message": "This Telegram account has no existing dialog with the recipient; the account only writes to people it already has a conversation with."
                  },
                  "RequestId": "req_7a42"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "`ServiceUnavailable`: o canal está fora do ar ou não está configurado. Traz `Retry-After`; repita depois.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "telegramConta.Send",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/flags": {
      "get": {
        "operationId": "ConfigFlagsDaSessao",
        "x-asender-authz": "session",
        "tags": [
          "bff-configuracao"
        ],
        "summary": "As feature flags avaliadas para a conta da sessão.",
        "description": "A conta vem do token verificado. Core fora = `{Flags: null, Degradado: true}`: a tela usa o padrão dela (piso 10).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "Flags": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "boolean"
                      }
                    },
                    "Degradado": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "configH.FlagsDaSessao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/dns/conexoes": {
      "get": {
        "operationId": "BffListDnsConexoes",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Lista as conexões Cloudflare da conta (nunca devolve o token).",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{conexoes:[{id, provedor, rotulo, token_final, zonas, criado_por, criado_em}]}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ListarConexoes",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateDnsConexao",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Conecta um token da Cloudflare à conta.",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.\nCorpo `{rotulo, token}` (token de 20 a 512 caracteres). O serviço verifica o token na Cloudflare, lista\nas zonas que ele alcança, cifra e grava; o token **nunca** volta em resposta e **nunca** é logado\n(nem no BFF, nem no log de requisições da API: o campo `token` é redigido). Corpo até 64 KiB.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "$ref": "#/components/parameters/platform_DnsIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "rotulo",
                  "token"
                ],
                "properties": {
                  "rotulo": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "token": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 512,
                    "writeOnly": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criada: a conexão, sem o token."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          },
          "default": {
            "description": "Erro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsProblema"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.CriarConexao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/dns/conexoes/{cid}": {
      "delete": {
        "operationId": "BffDeleteDnsConexao",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Apaga uma conexão (o token cifrado é apagado).",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "cid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^dnc_[0-9a-f]{16}$"
            },
            "description": "Id da conexão."
          },
          {
            "$ref": "#/components/parameters/platform_DnsIdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{apagada: true, id}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          },
          "default": {
            "description": "Erro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsProblema"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ApagarConexao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/dns/zonas": {
      "get": {
        "operationId": "BffListDnsZonas",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Zonas de todas as conexões da conta.",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{zonas:[{id, nome, conexao_id}], avisos:[{conexao_id, codigo}]}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ListarZonas",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/dns/zonas/{zona}/registros": {
      "get": {
        "operationId": "BffListDnsRegistros",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Registros de uma zona.",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "$ref": "#/components/parameters/platform_DnsZonaPath"
          },
          {
            "name": "tipo",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nome",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pagina",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "por_pagina",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{registros, total, pagina, por_pagina}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          },
          "default": {
            "description": "Erro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsProblema"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ListarRegistros",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateDnsRegistro",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Cria um registro (aceita `?seco=1`).",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.\nControles: `?seco=1` (plano sem alterar), `X-Asender-Confirmar` (nome do registro; MX/SPF/DMARC do apex) e `Idempotency-Key`, repassados ao serviço.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "$ref": "#/components/parameters/platform_DnsZonaPath"
          },
          {
            "name": "seco",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            },
            "description": "Devolve o plano sem tocar a Cloudflare."
          },
          {
            "$ref": "#/components/parameters/platform_DnsConfirmar"
          },
          {
            "$ref": "#/components/parameters/platform_DnsIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Corpo do serviço de DNS (campos em português: tipo, nome, conteudo, ttl, prioridade, proxied, comentario)."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK (plano com `seco`); sem `seco` o serviço responde 201 com `{registro}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          },
          "default": {
            "description": "Erro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsProblema"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.CriarRegistro",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/dns/zonas/{zona}/registros/{rid}": {
      "patch": {
        "operationId": "BffUpdateDnsRegistro",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Altera parcialmente um registro (aceita `?seco=1`).",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.\nControles: `?seco=1`, `X-Asender-Confirmar` e `Idempotency-Key`, repassados ao serviço.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "$ref": "#/components/parameters/platform_DnsZonaPath"
          },
          {
            "$ref": "#/components/parameters/platform_DnsRegistroPath"
          },
          {
            "name": "seco",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            },
            "description": "Devolve o plano sem tocar a Cloudflare."
          },
          {
            "$ref": "#/components/parameters/platform_DnsConfirmar"
          },
          {
            "$ref": "#/components/parameters/platform_DnsIdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Corpo do serviço de DNS (campos em português: tipo, nome, conteudo, ttl, prioridade, proxied, comentario)."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK: `{registro}` ou o plano."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "413": {
            "$ref": "#/components/responses/platform_PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          },
          "default": {
            "description": "Erro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsProblema"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.AlterarRegistro",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "BffDeleteDnsRegistro",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Apaga um registro (exige X-Asender-Confirmar).",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.\nExige `X-Asender-Confirmar` com o nome do registro (`428 ConfirmacaoNecessaria` sem ele); aceita `?seco=1` e `Idempotency-Key`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "$ref": "#/components/parameters/platform_DnsZonaPath"
          },
          {
            "$ref": "#/components/parameters/platform_DnsRegistroPath"
          },
          {
            "name": "seco",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            },
            "description": "Devolve o plano sem tocar a Cloudflare."
          },
          {
            "$ref": "#/components/parameters/platform_DnsConfirmar"
          },
          {
            "$ref": "#/components/parameters/platform_DnsIdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{apagado: registro}` ou o plano."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          },
          "default": {
            "description": "Erro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsProblema"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ApagarRegistro",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/dns/alteracoes": {
      "get": {
        "operationId": "BffListDnsAlteracoes",
        "x-asender-authz": "session",
        "tags": [
          "bff-dns"
        ],
        "summary": "Histórico das alterações de DNS da conta.",
        "description": "Painel de DNS (ADR-0131, A3). O `{id}` é autorizado por pertinência (`authorizeTenantParam`;\nconta alheia responde 404) **e** por papel: só `owner` e `admin` (deny-default; `developer`,\n`viewer`, suporte e papel indeterminado levam 403 `AdminRole`). Tenant = o `{id}` autorizado,\nator = o usuário da sessão, origem = `tela`; nada disso vem do cliente. Status e corpo do\nserviço de DNS voltam como vieram (RFC 9457 com `codigo`). `Cache-Control: no-store`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "zona",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Id da zona."
          },
          {
            "name": "pagina",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "por_pagina",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{alteracoes, total, pagina, por_pagina}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`403 AdminRole`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "description": "dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`)."
          },
          "default": {
            "description": "Erro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_DnsProblema"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ListarAlteracoes",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/logs": {
      "get": {
        "operationId": "BffListLogs",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Log de requisições da API da conta (painel, sem corpos).",
        "description": "Painel (sessão). O `{id}` vem do cliente e é autorizado por pertinência\n(`authorizeTenantParam`; conta alheia responde 404) **e** por papel: só\n`owner` e `admin` leem o log da conta (deny-default; `developer`, `viewer`\ne papel indeterminado levam 403). O tenant enviado ao serviço de logs é o\n`{id}` já validado.\nMesmos filtros e corpo de `GET /v1/logs`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Itens por página."
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco (`next` da página anterior): devolve os registros mais antigos. Adulterado ou de outra conta → 400. Não combine com `before`."
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor opaco: devolve os mais novos. `after` e `before` juntos → 400."
          },
          {
            "name": "status_class",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "4xx,5xx"
              ]
            },
            "description": "Lista separada por vírgula de `2xx`, `3xx`, `4xx`, `5xx`."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 100,
              "maximum": 599
            },
            "description": "Status HTTP exato."
          },
          {
            "name": "method",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "POST"
              ]
            },
            "description": "Verbo HTTP."
          },
          {
            "name": "path",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "/v1/emails/send"
              ]
            },
            "description": "Padrão de rota exato (não o caminho com ids)."
          },
          {
            "name": "api_key_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Id de uma ou mais chaves da conta, separados por vírgula."
          },
          {
            "name": "error_code",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "ValidationError"
              ]
            },
            "description": "Código do envelope de erro devolvido."
          },
          {
            "name": "idempotency_key",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Chave de idempotência efetiva da requisição."
          },
          {
            "name": "message_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Id de mensagem devolvido pelo envio (liga o log ao envio)."
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "live",
                "test"
              ]
            },
            "description": "Modo da chave usada."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339. Padrão: agora menos 24 h."
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "RFC 3339. Padrão: agora. Janela maior que 31 dias é reduzida a 31 dias antes de `until`."
          }
        ],
        "responses": {
          "200": {
            "description": "OK (corpo repassado do serviço de logs, em snake_case).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_LogsList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_LogsValidation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_LogsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.PainelList",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/logs/series": {
      "get": {
        "operationId": "BffLogsSeries",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Série temporal de requisições por classe de status.",
        "description": "Painel (sessão). O `{id}` vem do cliente e é autorizado por pertinência\n(`authorizeTenantParam`; conta alheia responde 404) **e** por papel: só\n`owner` e `admin` leem o log da conta (deny-default; `developer`, `viewer`\ne papel indeterminado levam 403). O tenant enviado ao serviço de logs é o\n`{id}` já validado.\nLê o rollup (não as partições): pontos ordenados por `t`, buckets vazios com zeros.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "bucket",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "hora",
                "dia"
              ],
              "default": "hora"
            },
            "description": "Largura do ponto."
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "live",
                "test"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK (corpo repassado do serviço de logs, em snake_case).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_LogsSerie"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_LogsValidation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_LogsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.PainelSeries",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/logs/resumo": {
      "get": {
        "operationId": "BffLogsResumo",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Resumo agregado da conta: total, taxa de erro, p50/p95, por rota e por código.",
        "description": "Painel (sessão). O `{id}` vem do cliente e é autorizado por pertinência\n(`authorizeTenantParam`; conta alheia responde 404) **e** por papel: só\n`owner` e `admin` leem o log da conta (deny-default; `developer`, `viewer`\ne papel indeterminado levam 403). O tenant enviado ao serviço de logs é o\n`{id}` já validado.\n`p95_ms` é estimado pelos buckets fixos do histograma.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "live",
                "test"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK (corpo repassado do serviço de logs, em snake_case).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_LogsResumo"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_LogsValidation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_LogsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.PainelResumo",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/logs/{logId}": {
      "get": {
        "operationId": "BffGetLog",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Detalhe de uma requisição (corpos redigidos).",
        "description": "Painel (sessão). O `{id}` vem do cliente e é autorizado por pertinência\n(`authorizeTenantParam`; conta alheia responde 404) **e** por papel: só\n`owner` e `admin` leem o log da conta (deny-default; `developer`, `viewer`\ne papel indeterminado levam 403). O tenant enviado ao serviço de logs é o\n`{id}` já validado.\nMesmo corpo de `GET /v1/logs/{id}`; id de outra conta = 404 idêntico ao inexistente.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "logId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^log_[0-9a-f]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK (corpo repassado do serviço de logs, em snake_case).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_LogDetalhe"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/platform_LogsValidation"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "503": {
            "$ref": "#/components/responses/platform_LogsUnavailable"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.PainelGet",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/envios-api": {
      "get": {
        "operationId": "BffListEnviosApi",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Destinatários dos envios feitos pela API (endereço completo, estado e motivo).",
        "description": "Painel (sessão). O `{id}` vem do cliente e é autorizado por pertinência\n(`authorizeTenantParam`; conta alheia responde 404) **e** por papel: só `owner` e\n`admin` leem (deny-default; `developer`, `viewer` e papel indeterminado levam 403).\nLê AO VIVO o serviço dono das mensagens, escopado no `{id}` validado — o log de\nrequisições guarda destinatários mascarados; este é o dado operacional. Inclui os\nsuprimidos (`status: suppressed`) com `reason` e `message`.\nSem `ids`: página por `status`, `q` (endereço), `cursor`, `limit`. Com `ids`\n(1 a 50 ids de mensagem, nenhum outro parâmetro): só os que existem na conta.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Estado da mensagem (ex.: `suppressed`, `sent`, `bounced`)."
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Busca no endereço do destinatário ou no assunto."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inteiro não negativo (`next_cursor` da página anterior)."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Até 50 ids de mensagem separados por vírgula."
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items",
                    "next_cursor",
                    "has_more"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string"
                          },
                          "from": {
                            "type": "string"
                          },
                          "subject": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string",
                            "description": "Motivo da supressão (vazio se não suprimida)."
                          },
                          "message": {
                            "type": "string",
                            "description": "Texto humano da supressão."
                          },
                          "created_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "queued_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "suppressed_until": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time",
                            "description": "ADR-0153: só em suprimidos com checagem OK; fim do prazo (nulo = não expira)."
                          },
                          "suppression_permanent": {
                            "type": "boolean",
                            "description": "Sem prazo (regra embutida, consentimento, manual sem prazo)."
                          },
                          "suppression_level": {
                            "type": "string",
                            "enum": [
                              "conta",
                              "global",
                              "regra"
                            ]
                          },
                          "suppression_degree": {
                            "type": "integer"
                          },
                          "suppression_degrees_total": {
                            "type": "integer"
                          },
                          "suppression_reason_text": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "description": "Parâmetro desconhecido ou inválido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          },
          "502": {
            "description": "Serviço de mensagens indisponível.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.List",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "operationId": "OAuthProtectedResource",
        "x-asender-authz": "public",
        "tags": [
          "mcp"
        ],
        "summary": "Protected Resource Metadata (RFC 9728) da API REST `https://api.asender.net`.",
        "description": "Documento público para clientes OAuth descobrirem o servidor de autorização (`auth.asender.net`),\nos escopos e a documentação. É o alvo do `resource_metadata` do `WWW-Authenticate` dos 401 de `/v1`.\nCache de 5 minutos; CORS liberado (`Access-Control-Allow-Origin: *`, preflight `OPTIONS` → 204).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ProtectedResourceMetadata"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "prmRaiz.Servir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/.well-known/oauth-protected-resource/mcp": {
      "get": {
        "operationId": "OAuthProtectedResourceMcp",
        "x-asender-authz": "public",
        "tags": [
          "mcp"
        ],
        "summary": "Protected Resource Metadata (RFC 9728) do servidor MCP `https://api.asender.net/mcp`.",
        "description": "Igual ao da raiz, com `resource` = `MCP_RECURSO`. `404` quando o MCP está desligado (`MCP_ATIVO=false`).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ProtectedResourceMetadata"
                }
              }
            }
          },
          "404": {
            "description": "MCP desligado."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "prmMCP.Servir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mcp": {
      "get": {
        "operationId": "McpGet",
        "x-asender-authz": "public",
        "tags": [
          "mcp"
        ],
        "summary": "Não há stream SSE (servidor stateless).",
        "responses": {
          "405": {
            "description": "Method Not Allowed (`Allow: POST, OPTIONS`)."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "Onde é usada: server.routes.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.NaoPermitido",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "McpDelete",
        "x-asender-authz": "public",
        "tags": [
          "mcp"
        ],
        "summary": "Não há sessão para encerrar (servidor stateless).",
        "responses": {
          "405": {
            "description": "Method Not Allowed (`Allow: POST, OPTIONS`)."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "Onde é usada: server.routes.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.NaoPermitido",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "McpPost",
        "x-asender-authz": "apikey",
        "tags": [
          "mcp"
        ],
        "summary": "Servidor MCP remoto (Streamable HTTP stateless, JSON-RPC 2.0, somente leitura).",
        "description": "Um POST por mensagem JSON-RPC (sem sessão, sem SSE, sem lote). Métodos: `initialize`,\n`notifications/initialized` (→ 202), `ping`, `tools/list`, `tools/call`. Versões aceitas em\n`MCP-Protocol-Version`: `2025-11-25`, `2025-06-18`, `2025-03-26` (ausente = `2025-03-26`).\n\n**Autenticação:** `Authorization: Bearer` com JWT de app OAuth cujo `aud` contém\n`https://api.asender.net/mcp` **ou** `https://api.asender.net`, ou chave `sk_*`. O escopo é por\n**ferramenta**: `tools/list` só mostra as que o token/chave pode usar e `tools/call` sem escopo devolve\n`isError`. Cada ferramenta roda o mesmo handler da rota REST (mesmo escopo, produto e PDP do usuário).\nFalha de negócio (400/403/404/503 da rota) volta como resultado com `isError: true`; só erro de\nprotocolo usa códigos JSON-RPC (-32700, -32600, -32601, -32602).\n\n`Origin` presente e fora da lista (API, AS, loopback, `MCP_ORIGENS_PERMITIDAS`) → 403. Corpo ≤ 1 MiB.\n`401` leva `WWW-Authenticate: Bearer [error=\"invalid_token\", ]resource_metadata=\"…/oauth-protected-resource/mcp\"`.",
        "security": [
          {
            "ApiKeyBearer": []
          },
          {
            "OAuthBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jsonrpc",
                  "method"
                ],
                "properties": {
                  "jsonrpc": {
                    "const": "2.0"
                  },
                  "id": {},
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta JSON-RPC (`result` ou `error`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "Notificação ou resposta do cliente aceita",
            "sem corpo.": null
          },
          "400": {
            "description": "JSON inválido",
            "lote": null,
            "ou `MCP-Protocol-Version` desconhecida.": null
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Origin não permitido."
          },
          "413": {
            "description": "Corpo acima de 1 MiB."
          },
          "429": {
            "$ref": "#/components/responses/platform_Throttled"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Post",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/convites": {
      "get": {
        "operationId": "BffMeConvites",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Convites pendentes/expirados para os e-mails VERIFICADOS do usuário.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "convites": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "examples": [
                              "cvt_0123456789abcdef0123456789abcdef"
                            ]
                          },
                          "tenant": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "nome": {
                                "type": "string"
                              }
                            }
                          },
                          "papel": {
                            "type": "string"
                          },
                          "convidado_por_nome": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "email": {
                            "type": "string",
                            "format": "email"
                          },
                          "expira_em": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pendente",
                              "expirado"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "503": {
            "description": "`IdentityUnavailable` — auth fora (nunca lista vazia mentindo); `InvitationsUnavailable` — core fora."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "Onde é usada: seção Convites da \"Minha conta\" (auth_front `/conta/convites`).",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "convites.Listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/convites/contagem": {
      "get": {
        "operationId": "BffMeConvitesContagem",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Quantos convites pendentes (badge). Falha → `{pendentes:null}` 200.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pendentes": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "null = não deu para contar (auth/core fora); o badge some."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "Onde é usada: badge do AppHeader e faixa do hub (Frente E), em polling de 60s. QUALQUER falha (auth, core, sessão) vira 200 `{pendentes:null}`: o badge some e nada mais quebra (piso 10). Duas idas leves (auth + COUNT indexado).",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "convites.Contar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/convites/{id}/aceitar": {
      "post": {
        "operationId": "BffMeConviteAceitar",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Aceita convite por public_id (idempotente; herda acessos).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Idempotente. `user_id` é o da SESSÃO e os e-mails são os verificados do auth — nada vem do corpo. Depois do aceite, a mesma herança de acessos do link (owner/admin herdam as ferramentas).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "member": {
                      "type": "object",
                      "properties": {
                        "tenant_id": {
                          "type": "string"
                        },
                        "user_id": {
                          "type": "string"
                        },
                        "role": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "description": "Inexistente, de outro e-mail, id fora do formato `cvt_…` ou usuário sem e-mail verificado (mesma resposta)."
          },
          "409": {
            "description": "`Declined` ou `AlreadyAccepted`."
          },
          "410": {
            "description": "`Expired` ou `Revoked`."
          },
          "429": {
            "description": "`Throttling` — teto de 30 decisões de convite (aceitar+recusar) por usuário por hora, com `Retry-After`."
          },
          "502": {
            "description": "core indisponível."
          },
          "503": {
            "description": "`IdentityUnavailable` — auth fora."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "Onde é usada: cartão do convite. Idempotente (duplo clique = mesmo member).\n\nEfeitos: pertinência no core + herança de acessos no auth (falha da herança não desfaz o aceite — mesma regra do link) + linha de auditoria no log.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "convites.Aceitar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/convites/{id}/recusar": {
      "post": {
        "operationId": "BffMeConviteRecusar",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Recusa convite (idempotente).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Recusado (idempotente)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "description": "Inexistente, de outro e-mail ou id fora do formato (mesma resposta)."
          },
          "409": {
            "description": "`AlreadyAccepted`."
          },
          "429": {
            "description": "`Throttling` — mesmo teto do aceitar (30/h por usuário, somados)."
          },
          "502": {
            "description": "core indisponível."
          },
          "503": {
            "description": "`IdentityUnavailable` — auth fora."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "Onde é usada: cartão do convite. Idempotente.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "convites.Recusar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me": {
      "get": {
        "operationId": "BffMeObter",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Minha conta: usuário, fatores e reauth vigente (repasse de GET /v1/me).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "operationId": "BffMeAtualizarPerfil",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Troca o nome (repasse de PATCH /v1/me).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/reauth/start": {
      "post": {
        "operationId": "BffMeReauthStart",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Inicia a reautenticação (repasse).",
        "description": "Corpo `{\"metodo\": \"...\", \"email_id\": \"<uuid opcional>\"}` — `email_id` só para `email_otp`: o e-mail VERIFICADO que recebe o código (conferido no auth: dono + verificado; outro → `400 MetodoIndisponivel`). Resposta 202 (repassada): `{metodo, destinos?: [{email_id, mascarado, primario}], enviado_para?}` — `destinos`/`enviado_para` só para `email_otp`; primário primeiro, o 1º é o padrão.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "metodo"
                ],
                "properties": {
                  "metodo": {
                    "type": "string",
                    "enum": [
                      "email_otp",
                      "totp",
                      "senha",
                      "codigo_recuperacao",
                      "passkey"
                    ]
                  },
                  "email_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "origem": {
                    "type": "string",
                    "format": "uri",
                    "description": "Só `passkey`: a origem do authfront (allowlist no auth)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Prova preparada; `destinos` e `enviado_para` para `email_otp`; `desafio_id` e `opcoes` (WebAuthn, base64url) para `passkey` (formato no description)."
          },
          "400": {
            "description": "`MetodoIndisponivel` (método que a conta não tem, ou `email_id` alheio/pendente/inexistente)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "429": {
            "description": "`TooManyAttempts` — cap de códigos por janela."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/reauth/confirm": {
      "post": {
        "operationId": "BffMeReauthConfirm",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Confirma a reautenticação na sessão (repasse; 429 com Retry-After).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/senha": {
      "post": {
        "operationId": "BffMeTrocarSenha",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Troca a senha (reauth senha atual|email_otp|totp|passkey|codigo_recuperacao — P4).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/emails": {
      "get": {
        "operationId": "BffMeListarEmails",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "E-mails da conta.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffMeAdicionarEmail",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Adiciona e-mail — 202 neutro sempre (reauth totp se tiver, senão email_otp).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "202": {
            "description": "Neutro: `{ok:true}` exista ou não o endereço."
          },
          "400": {
            "description": "`ValidationError` (endereço inválido) ou `InvalidRequest` (campo desconhecido)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "429": {
            "description": "`TooManyAttempts` — 10 e-mails adicionados por dia."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/emails/{id}": {
      "delete": {
        "operationId": "BffMeRemoverEmail",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Remove e-mail (reauth totp se tiver, senão email_otp).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/emails/{id}/primario": {
      "post": {
        "operationId": "BffMeDefinirPrimario",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Torna o e-mail primário (mesma reauth).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/emails/{id}/reenviar": {
      "post": {
        "operationId": "BffMeReenviarVerificacao",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Reenvia a verificação.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Link reenviado (ou nada a fazer: já verificado)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "422": {
            "description": "`EnderecoNaoRecebe` — o servidor do destinatário recusou o endereço de vez."
          },
          "429": {
            "description": "`TooManyAttempts` — cap de reenvio por janela."
          },
          "502": {
            "description": "auth indisponível, ou `EnvioRecusado` (recusa permanente por política do destino)."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/fatores": {
      "get": {
        "operationId": "BffMeListarFatores",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Inventário de fatores + remoções pendentes (leitura).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{fatores:{senha,email_otp,totp,codigos_restantes,passkeys}, remocoes_pendentes:[{id,fator,executar_em}]}` (auth MeFatores; array vazio quando não há)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "description": "Rota fora da allowlist do contrato."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/passkeys": {
      "get": {
        "operationId": "BffMeListarPasskeys",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Chaves de acesso (passkeys) da conta (leitura; sem chave pública nem credential id).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{passkeys:[{id,nome,criado_em,usado_em,sincronizada,rp_id}]}` (auth MePasskeys)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`SessaoNecessaria` (credencial sem sessão do auth)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/passkeys/registro/start": {
      "post": {
        "operationId": "BffMePasskeyRegistroStart",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Começa o cadastro de uma chave de acesso (reauth email_otp|senha|totp|passkey).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "origem"
                ],
                "properties": {
                  "origem": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{desafio_id, opcoes}` para navigator.credentials.create (repassado)."
          },
          "400": {
            "description": "`OrigemRecusada` / corpo inválido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Details.metodos_aceitos`/`metodos`) ou `ImpersonationReadOnly`."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato."
          },
          "502": {
            "description": "auth indisponível."
          },
          "503": {
            "description": "`PasskeyIndisponivel`."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/passkeys/registro/finish": {
      "post": {
        "operationId": "BffMePasskeyRegistroFinish",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Grava a chave de acesso (resposta do autenticador).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "desafio_id",
                  "credencial"
                ],
                "properties": {
                  "desafio_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "nome": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "credencial": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "`{passkey}` (repassado)."
          },
          "400": {
            "description": "`DesafioExpirado`, `PasskeyRecusada` ou `ValidationError`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` ou `ImpersonationReadOnly`."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato."
          },
          "409": {
            "description": "`PasskeyJaRegistrada`."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/passkeys/{id}": {
      "patch": {
        "operationId": "BffMePasskeyRenomear",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Renomeia uma chave de acesso (sem reauth).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "nome"
                ],
                "properties": {
                  "nome": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{passkey}` (repassado)."
          },
          "400": {
            "description": "`ValidationError`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`SessaoNecessaria` ou `ImpersonationReadOnly`."
          },
          "404": {
            "description": "Inexistente ou de outra pessoa (indistinguíveis), ou rota fora da allowlist."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "BffMePasskeyRemover",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Remove uma chave de acesso (reauth email_otp|totp|codigo_recuperacao; último forte agenda 24h).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{removida:true}` — saiu na hora."
          },
          "202": {
            "description": "`{id, fator:\"passkey\", agendado_para, passkey_id}` — último fator forte: remoção em 24h, cancelável."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` ou `ImpersonationReadOnly`."
          },
          "404": {
            "description": "Inexistente ou de outra pessoa (indistinguíveis), ou rota fora da allowlist."
          },
          "409": {
            "description": "`MinimoDeFatores`."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/totp/setup": {
      "post": {
        "operationId": "BffMeTOTPSetup",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Gera TOTP pendente (reauth email_otp|senha).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{otpauth_url, secret}` (auth MeTOTPSetup), repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "409": {
            "description": "`AlreadyEnabled` (app já ativo)."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/totp/confirm": {
      "post": {
        "operationId": "BffMeTOTPConfirmar",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Liga o TOTP (reauth email_otp|senha).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{ok, codigos_recuperacao}` (auth MeTOTPConfirmado): 10 códigos XXXX-XXXX-XXXX na 1ª ativação, vazio se já havia códigos vivos."
          },
          "400": {
            "description": "`InvalidCode`, `TOTPNaoIniciado` ou `InvalidRequest`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "409": {
            "description": "`AlreadyEnabled`."
          },
          "429": {
            "description": "`TooManyAttempts` (freio por usuário, o mesmo do login)."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/totp": {
      "delete": {
        "operationId": "BffMeTOTPRemover",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Agenda a remoção do TOTP em 24h (reauth email_otp|codigo_recuperacao).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "202": {
            "description": "`{id, fator, agendado_para}` (auth MeRemocaoAgendada); idempotente."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "409": {
            "description": "`NotEnabled` ou `MinimoDeFatores` (ficaria com menos de 2 fatores)."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/remocoes/{id}": {
      "delete": {
        "operationId": "BffMeCancelarRemocao",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Cancela a remoção agendada.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/codigos-recuperacao": {
      "post": {
        "operationId": "BffMeGerarCodigos",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Gera 10 códigos de recuperação (reauth totp|email_otp).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{codigos:[10]}` no formato XXXX-XXXX-XXXX (auth MeCodigos), mostrados uma vez; os anteriores deixam de valer."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/sessoes": {
      "get": {
        "operationId": "BffMeListarSessoes",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Sessões vivas do usuário.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/sessoes/{id}": {
      "delete": {
        "operationId": "BffMeRevogarSessao",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Revoga uma sessão própria.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/sessoes/encerrar-outras": {
      "post": {
        "operationId": "BffMeEncerrarOutras",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Encerra as outras sessões (qualquer reauth).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/me/senhas-app": {
      "get": {
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "tags": [
          "api"
        ],
        "summary": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.",
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "tags": [
          "api"
        ],
        "summary": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.",
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "codigo"
      }
    },
    "/api/me/senhas-app/{id}": {
      "delete": {
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "tags": [
          "api"
        ],
        "summary": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.",
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "codigo"
      }
    },
    "/api/me/eventos": {
      "get": {
        "operationId": "BffMeListarEventos",
        "x-asender-authz": "session",
        "tags": [
          "bff-me"
        ],
        "summary": "Trilha de segurança paginada (só `antes` e `limite` atravessam).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "antes",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Corpo do contrato §4.2 do auth, repassado sem reinterpretação."
          },
          "400": {
            "description": "`InvalidRequest` — cursor `antes` ou `limite` inválido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve)."
          },
          "404": {
            "description": "Rota fora da allowlist do contrato, ou recurso inexistente/alheio."
          },
          "502": {
            "description": "auth indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.\n\nOnde é usada: grupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "me.Repassar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/passkey/login/start": {
      "post": {
        "operationId": "BffPasskeyLoginStart",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Começa o login SEM senha com chave de acesso (público, teto por IP do auth).",
        "description": "Corpo `{origem}` (o authfront põe a origem do browser, conferida contra o host). Sem\ne-mail: o autenticador escolhe a conta. Resposta do auth repassada: `{desafio_id, opcoes}`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "origem"
                ],
                "properties": {
                  "origem": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{desafio_id, opcoes}`."
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "429": {
            "description": "Teto por IP."
          },
          "502": {
            "description": "auth indisponível."
          },
          "503": {
            "description": "`PasskeyIndisponivel`."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.PasskeyLoginStart",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/auth/passkey/login/finish": {
      "post": {
        "operationId": "BffPasskeyLoginFinish",
        "x-asender-authz": "public",
        "tags": [
          "bff-auth"
        ],
        "summary": "Termina o login com chave de acesso e devolve a sessão (formato do /api/auth/2fa).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "desafio_id",
                  "credencial"
                ],
                "properties": {
                  "desafio_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "credencial": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "lembrar": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{Token, UserId, ExpiresAt}` — o authfront grava o cookie HttpOnly."
          },
          "400": {
            "$ref": "#/components/responses/platform_Validation"
          },
          "401": {
            "description": "`PasskeyRecusada` (qualquer recusa da credencial — sem oráculo) ou `DesafioExpirado`."
          },
          "429": {
            "description": "Teto por IP."
          },
          "502": {
            "description": "auth indisponível ou sem token na resposta."
          },
          "503": {
            "description": "`PasskeyIndisponivel`."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "repassa a asserção; no 200, devolve `{Token, UserId, ExpiresAt}`. Recusas do auth (401 PasskeyRecusada/DesafioExpirado, 400 OrigemRecusada, 503 PasskeyIndisponivel) passam com o status e o corpo de lá.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "authH.PasskeyLoginFinish",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/oauth-apps": {
      "get": {
        "operationId": "BffListOAuthApps",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Lista os apps OAuth (\"Entrar com Asender\") da conta.",
        "description": "Portal \"Aplicativos\" (ADR-0130, D4). O `{id}` é autorizado por pertinência\n(`authorizeTenantParam`; conta alheia responde 404) e o BFF repassa o Bearer da\nsessão ao auth (`GET /v1/oauth/meus-apps?conta={id}`), que confere owner/admin no\ncore (membro sem papel: 403 `PapelInsuficiente`). Resposta `Cache-Control: no-store`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{apps:[app]}` (corpo do auth)."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Membro sem papel owner/admin (`PapelInsuficiente`)."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffCreateOAuthApp",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Cria um app OAuth na conta (o client_secret volta UMA vez).",
        "description": "O campo `conta` do corpo é SEMPRE sobrescrito pelo `{id}` da rota. Corpo até 64 KiB.\nPara app confidencial a resposta 201 traz `client_secret` uma única vez\n(`Cache-Control: no-store`); nada é guardado nem logado no BFF.\nErros do auth intactos: 409 `LimiteDeApps` (10 por conta), 429 `MuitasCriacoes`\n(com `Retry-After`), 422 validação.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nome",
                  "redirect_uris",
                  "escopos"
                ],
                "properties": {
                  "nome": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "site_url": {
                    "type": "string"
                  },
                  "logo_url": {
                    "type": "string"
                  },
                  "redirect_uris": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": {
                      "type": "string"
                    }
                  },
                  "escopos": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string"
                    }
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "confidential",
                      "public"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Criado: `{app, client_secret?, aviso}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Sem papel owner/admin."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`LimiteDeApps`."
          },
          "413": {
            "description": "Corpo acima de 64 KiB."
          },
          "422": {
            "description": "Validação do auth."
          },
          "429": {
            "description": "`MuitasCriacoes` (Retry-After)."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/oauth-apps/{app}": {
      "get": {
        "operationId": "BffGetOAuthApp",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Detalhe de um app OAuth da conta.",
        "description": "App de outra conta responde 404 (o BFF confere `app.conta == {id}`).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{app}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "Sem papel owner/admin."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "operationId": "BffUpdateOAuthApp",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Edita campos do app (nome, site_url, logo_url, redirect_uris, escopos).",
        "description": "Ampliar escopos de app publicado marca `revisao_pendente`. Corpo até 64 KiB.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK: `{app}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "422": {
            "description": "Validação do auth."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "operationId": "BffDeleteOAuthApp",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Exclui (desativa) o app e revoga grants e refresh tokens.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Excluído."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Excluir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/oauth-apps/{app}/segredo": {
      "post": {
        "operationId": "BffRotateOAuthAppSecret",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Gira o client_secret (o novo volta UMA vez).",
        "description": "Resposta `no-store`; 409 `AppPublico` para app sem segredo.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{client_id, client_secret, aviso}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`AppPublico`."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.GirarSegredo",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/oauth-apps/{app}/revisao": {
      "post": {
        "operationId": "BffRequestOAuthAppReview",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Pede a revisão do app pela equipe.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{app}` em `em_revisao`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`EstadoInvalido`."
          },
          "422": {
            "description": "`RevisaoIncompleta` (site_url vazio)."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.PedirRevisao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/oauth-apps/{app}/revisoes": {
      "get": {
        "operationId": "BffListOAuthAppReviews",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Histórico de revisões do app.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{revisoes:[…]}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Revisoes",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/oauth-apps/{app}/testadores": {
      "get": {
        "operationId": "BffListOAuthAppTesters",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Lista os testadores do app (limite 25).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK: `{testadores:[…], limite}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Testadores",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "BffInviteOAuthAppTester",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Convida um testador (por `email` ou `user_id`).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string"
                  },
                  "user_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Convidado: `{testador}`."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "description": "`UsuarioNaoEncontrado` ou conta/app inexistente."
          },
          "409": {
            "description": "`LimiteDeTestadores`."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Convidar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/oauth-apps/{app}/testadores/{usr}": {
      "delete": {
        "operationId": "BffRemoveOAuthAppTester",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Remove um testador do app.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "app",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usr",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.RemoverTestador",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/resumo": {
      "get": {
        "operationId": "BffResumoDoHub",
        "x-asender-authz": "session",
        "tags": [
          "bff-resumo"
        ],
        "summary": "Home do hub: o resumo de cada ferramenta que a pessoa acessa na conta.",
        "description": "O BFF pergunta ao PDP (`/v1/authz/eu`) quais ferramentas a pessoa acessa na conta (resolvida por pertinência; Ferramenta que falha, expira ou responde fora do contrato vem como `{ferramenta, nome, subdominio, indisponivel: true}` — o resto responde normalmente. Se a ferramenta RECUSA o acesso (403 membro sem o produto, 404), o bloco fica ausente. `atendimento` não vira bloco: quem tem CRM e o produto `atendimento` no PDP recebe até 3 indicadores do chat DENTRO do bloco do CRM (`modulo: \"chat\"`, link para as conversas do CRM; falha do atendimento só tira o chat, ADR-0080). Links de indicador são sempre caminhos relativos à ferramenta (o BFF descarta os demais).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "periodo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "periodo",
                    "ferramentas"
                  ],
                  "properties": {
                    "periodo": {
                      "type": "string",
                      "enum": [
                        "7d",
                        "30d",
                        "90d"
                      ]
                    },
                    "ferramentas": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "ferramenta",
                          "nome",
                          "subdominio"
                        ],
                        "properties": {
                          "ferramenta": {
                            "type": "string",
                            "examples": [
                              "crm"
                            ]
                          },
                          "nome": {
                            "type": "string",
                            "examples": [
                              "CRM"
                            ]
                          },
                          "subdominio": {
                            "type": "string",
                            "examples": [
                              "crm"
                            ]
                          },
                          "indisponivel": {
                            "type": "boolean"
                          },
                          "periodo": {
                            "type": "object",
                            "properties": {
                              "de": {
                                "type": "string",
                                "format": "date"
                              },
                              "ate": {
                                "type": "string",
                                "format": "date"
                              },
                              "chave": {
                                "type": "string",
                                "enum": [
                                  "7d",
                                  "30d",
                                  "90d"
                                ]
                              }
                            }
                          },
                          "indicadores": {
                            "type": "array",
                            "maxItems": 6,
                            "items": {
                              "type": "object",
                              "properties": {
                                "chave": {
                                  "type": "string",
                                  "examples": [
                                    "leads_novos"
                                  ]
                                },
                                "rotulo": {
                                  "type": "string",
                                  "examples": [
                                    "Leads novos"
                                  ]
                                },
                                "valor": {
                                  "type": "number"
                                },
                                "anterior": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "formato": {
                                  "type": "string",
                                  "enum": [
                                    "numero",
                                    "percentual",
                                    "moeda",
                                    "duracao"
                                  ]
                                },
                                "link": {
                                  "type": "string",
                                  "examples": [
                                    "/t/{tenant}/crm/leads"
                                  ]
                                },
                                "modulo": {
                                  "type": "string",
                                  "examples": [
                                    "chat"
                                  ],
                                  "description": "Só o BFF escreve: indicador de módulo mesclado no bloco."
                                }
                              }
                            }
                          },
                          "series": {
                            "type": "array",
                            "maxItems": 2,
                            "items": {
                              "type": "object",
                              "properties": {
                                "chave": {
                                  "type": "string"
                                },
                                "rotulo": {
                                  "type": "string"
                                },
                                "tipo": {
                                  "type": "string",
                                  "examples": [
                                    "linha"
                                  ]
                                },
                                "pontos": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "data": {
                                        "type": "string",
                                        "format": "date"
                                      },
                                      "valor": {
                                        "type": "number"
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`ValidationError` — periodo fora de 7d|30d|90d."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`NoTenant` — o usuário ainda não pertence a conta nenhuma."
          },
          "404": {
            "description": "Conta alheia no `X-Asender-Tenant` (mesma resposta de inexistente)."
          },
          "429": {
            "description": "`Throttling` — teto por principal."
          },
          "502": {
            "description": "core ou PDP (auth) indisponível — sem eles não há como saber o que a pessoa vê."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "resumo.Listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/resumo/marketing": {
      "get": {
        "operationId": "BffResumoDoMarketing",
        "x-asender-authz": "session",
        "tags": [
          "bff-resumo"
        ],
        "summary": "Home do marketing: o `/v1/resumo` do asender_mkt_api, repassado sem reinterpretação.",
        "description": "Rota do produto `marketing` (PEP: sem acesso à ferramenta → recusa do PEP). O corpo segue o contrato comum de resumo em snake/minúsculo, sem PascalMap: `{ferramenta, periodo{de,ate,chave}, indicadores[{chave,rotulo,valor,anterior,formato,link}], series[{chave,rotulo,tipo,pontos[{data,valor}]}]}`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "periodo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "O payload do `/v1/resumo` do marketing, verbatim."
          },
          "400": {
            "description": "`ValidationError` — periodo fora de 7d|30d|90d."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`NoTenant`."
          },
          "404": {
            "description": "Conta alheia, ou sem acesso ao produto `marketing` (PEP)."
          },
          "429": {
            "description": "`Throttling` — teto por principal."
          },
          "502": {
            "description": "mkt_api ou core indisponível."
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "resumo.Marketing",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/logo/{variante}": {
      "get": {
        "operationId": "BffLerLogoDaConta",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Imagem do logo da empresa (lançador e cabeçalho dos fronts).",
        "description": "O `{id}` é autorizado contra a pertinência do usuário\n(`authorizeTenantParam`) antes de o core ser perguntado; conta alheia\nresponde 404. Os bytes são o PNG que o core recodificou (migration 031);\na resposta leva `Content-Type: image/png` FIXO, `nosniff`, CSP `sandbox`\ne `Cache-Control: private` — imutável por um ano quando `?v=` é a versão\natual (a lista `GET /api/tenants` traz `LogoQuadrado`/`LogoHorizontal`).\nSó leitura: enviar/remover é governança no admin.asender.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "variante",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "quadrado",
                "horizontal"
              ]
            }
          },
          {
            "name": "v",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "logos.Ler",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/enderecos/consulta": {
      "get": {
        "operationId": "BffConsultarEndereco",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Endereço de um CEP/zipcode (widget CampoEndereco dos fronts).",
        "description": "Sem conta no caminho: o endereço de um CEP é dado\npúblico — a guarda é a sessão + o rate limit por principal. País/CEP\nfora do formato voltam `422` sem hop. 200/404/422/503 do core passam como\nvieram; qualquer outra resposta (ou core fora) vira `502`. Falha nunca\nbloqueia o formulário: o widget segue com os campos manuais.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "pais",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z]{2}$",
              "examples": [
                "BR"
              ]
            }
          },
          {
            "name": "cep",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9 -]{2,16}$",
              "examples": [
                "01310100"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Achado (sem número/complemento).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "pais",
                    "cep",
                    "fonte"
                  ],
                  "properties": {
                    "pais": {
                      "type": "string",
                      "examples": [
                        "BR"
                      ]
                    },
                    "cep": {
                      "type": "string",
                      "examples": [
                        "01310-100"
                      ]
                    },
                    "estado": {
                      "type": "string",
                      "examples": [
                        "SP"
                      ]
                    },
                    "estado_nome": {
                      "type": "string",
                      "examples": [
                        "São Paulo"
                      ]
                    },
                    "cidade": {
                      "type": "string",
                      "examples": [
                        "São Paulo"
                      ]
                    },
                    "bairro": {
                      "type": "string",
                      "examples": [
                        "Bela Vista"
                      ]
                    },
                    "rua": {
                      "type": "string",
                      "examples": [
                        "Avenida Paulista"
                      ]
                    },
                    "fonte": {
                      "type": "string",
                      "enum": [
                        "viacep",
                        "brasilapi",
                        "zippopotam",
                        "cache"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "description": "CEP não encontrado ou país sem cobertura.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/platform_Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/platform_BadGateway"
          },
          "503": {
            "description": "Provedores fora; campos seguem manuais.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "enderecos.Consultar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/catalogo": {
      "get": {
        "operationId": "SuporteCatalogo",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Categorias, produtos, impactos e limites do formulário de chamado.",
        "description": "Repassa o catálogo do atendimento. Sessão impersonada recebe o catálogo (estático, sem conta).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{categorias, produtos, impactos, limites}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Catalogo",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados": {
      "get": {
        "operationId": "SuporteListarChamados",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Chamados do usuário (`escopo=usuario`) ou da conta (`escopo=conta`, exige governança).",
        "description": "A conta vem da pertinência (`X-Asender-Tenant`, slug ou id, só vale se o usuário pertence a ela; alheia → 404). Cada chamado sai com `solicitante {id, nome}` e `conta {id, nome, slug}`. Parâmetros fora da allowlist são descartados. Sessão impersonada: escopo `usuario` → lista vazia; escopo `conta` → 403 `SemGovernanca`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "escopo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "usuario",
                "conta"
              ],
              "default": "usuario"
            }
          },
          {
            "name": "estado",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "categoria",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 25
            }
          },
          {
            "name": "antes",
            "in": "query",
            "required": false,
            "description": "Cursor opaco (`proximo` da página anterior).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{chamados: Chamado[], proximo?}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "`ValidationError` — filtro ou cursor inválido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`SemGovernanca` (escopo conta sem ser admin) ou `NoTenant`."
          },
          "404": {
            "description": "Conta alheia."
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "operationId": "SuporteCriarChamado",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Abre um chamado.",
        "description": "Corpo estrito: campo desconhecido (`tenant_id`, `solicitante_*`, `autor`, `estado`, `prioridade`…) → 400 no BFF, sem tocar no atendimento. Qualquer membro da conta pode abrir (inclusive viewer). `Idempotency-Key` opcional (`[A-Za-z0-9._:-]{1,128}`); replay do mesmo usuário volta 200. Sessão impersonada → 403 `ImpersonationReadOnly`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "assunto",
                  "descricao",
                  "categoria",
                  "produto",
                  "impacto"
                ],
                "properties": {
                  "assunto": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 200
                  },
                  "descricao": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 20000
                  },
                  "categoria": {
                    "type": "string",
                    "enum": [
                      "duvida",
                      "problema_tecnico",
                      "cobranca",
                      "conta_acesso",
                      "sugestao",
                      "seguranca_abuso"
                    ]
                  },
                  "produto": {
                    "type": "string"
                  },
                  "impacto": {
                    "type": "string",
                    "enum": [
                      "parado",
                      "parcial",
                      "duvida"
                    ]
                  },
                  "autoriza_acesso_leitura": {
                    "type": "boolean",
                    "default": false
                  },
                  "relacionado_a": {
                    "type": "string",
                    "examples": [
                      "CH-2026-000001"
                    ]
                  },
                  "contexto": {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                      "pagina": {
                        "type": "string"
                      },
                      "versao_app": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replay da mesma `Idempotency-Key`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "201": {
            "description": "Chamado criado (`Location` aponta para `/api/suporte/chamados/{protocolo}`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "`ValidationError` — campo desconhecido ou JSON inválido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ImpersonationReadOnly` ou `NoTenant`."
          },
          "404": {
            "description": "Conta alheia."
          },
          "409": {
            "description": "`LimiteDeChamadosAbertos` ou `IdempotencyConflict`."
          },
          "422": {
            "description": "`ValidationError` do atendimento."
          },
          "429": {
            "description": "`Throttling`."
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/contagem": {
      "get": {
        "operationId": "SuporteContagemChamados",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Indicadores do menu (aguardando você, respostas não lidas, abertos).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "escopo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "usuario",
                "conta"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{aguardando_voce, respostas_nao_lidas, abertos}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`SemGovernanca`."
          },
          "404": {
            "description": "Conta alheia."
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "indicadores do menu (aguardando você, respostas não lidas, abertos).",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Contagem",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/{protocolo}": {
      "get": {
        "operationId": "SuporteObterChamado",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Um chamado do usuário (ou da conta, para quem governa).",
        "description": "Chamado inexistente, de outro usuário ou de outra conta → 404 indistinguível. Protocolo fora do formato `CH-AAAA-NNNNNN` → 404 sem chamar o atendimento.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "protocolo",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^CH-[0-9]{4}-[0-9]{6}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chamado com `solicitante` e `conta`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Obter",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/{protocolo}/itens": {
      "get": {
        "operationId": "SuporteItensDoChamado",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Linha do tempo pública do chamado (mensagens e eventos).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "protocolo",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^CH-[0-9]{4}-[0-9]{6}$"
            }
          },
          {
            "name": "limite",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "antes",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{itens: (mensagem|evento)[], proximo?}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "a linha do tempo pública (mensagens e eventos), paginada por cursor.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Itens",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/{protocolo}/mensagens": {
      "post": {
        "operationId": "SuporteEnviarMensagem",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Responde ao chamado (JSON ou multipart com até 3 anexos).",
        "description": "`application/json` (`{corpo}`) ou `multipart/form-data` (campo `corpo` + até 3 `anexo`, cada um ≤ 5 MiB). O multipart é reescrito em streaming só com esses campos; outro campo → 400. A resposta do cliente reabre `pendente`/`resolvido`. Chamado fechado → 409 `ChamadoFechado` (use `/reabrir`).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "protocolo",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^CH-[0-9]{4}-[0-9]{6}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "corpo"
                ],
                "properties": {
                  "corpo": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20000
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "corpo"
                ],
                "properties": {
                  "corpo": {
                    "type": "string"
                  },
                  "anexo": {
                    "type": "array",
                    "maxItems": 3,
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "`{id, criada_em, estado}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "`ValidationError` — campo não permitido ou anexos demais."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ImpersonationReadOnly`."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`ChamadoFechado`."
          },
          "413": {
            "description": "`PayloadTooLarge`."
          },
          "415": {
            "description": "`TipoDeAnexoNaoAceito`."
          },
          "429": {
            "description": "`Throttling`."
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.EnviarMensagem",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/{protocolo}/mensagens/{msgId}/anexos/{anexoId}": {
      "get": {
        "operationId": "SuporteBaixarAnexo",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Baixa um anexo (streaming).",
        "description": "Sempre `Content-Disposition: attachment`, `X-Content-Type-Options: nosniff` e `Cache-Control: private, no-store`. Anexo de protocolo alheio → 404. Em verificação de antivírus → 409 `AnexoEmVerificacao` (+ `Retry-After`); bloqueado → 403 `AnexoBloqueado`.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "protocolo",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^CH-[0-9]{4}-[0-9]{6}$"
            }
          },
          {
            "name": "msgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "anexoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Os bytes do anexo.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`AnexoBloqueado`."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`AnexoEmVerificacao` (com `Retry-After`)."
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.BaixarAnexo",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/{protocolo}/fechar": {
      "post": {
        "operationId": "SuporteFecharChamado",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Fecha o chamado.",
        "description": "Corpo `{}` (ou ausente). Já fechado → 409.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "protocolo",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^CH-[0-9]{4}-[0-9]{6}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chamado atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ImpersonationReadOnly`."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`ChamadoFechado`."
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Fechar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/{protocolo}/reabrir": {
      "post": {
        "operationId": "SuporteReabrirChamado",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Reabre o chamado dentro da janela.",
        "description": "Fora da janela → 409 `JanelaDeReaberturaExpirada` com `Details.relacionado_a_sugerido` (abra um novo chamado com `relacionado_a`).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "protocolo",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^CH-[0-9]{4}-[0-9]{6}$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "motivo": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chamado atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ImpersonationReadOnly`."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`JanelaDeReaberturaExpirada`."
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Reabrir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/{protocolo}/avaliacao": {
      "put": {
        "operationId": "SuporteAvaliarChamado",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Avalia o atendimento (nota 1..5), idempotente por ciclo.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "protocolo",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^CH-[0-9]{4}-[0-9]{6}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "nota"
                ],
                "properties": {
                  "nota": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5
                  },
                  "comentario": {
                    "type": "string",
                    "maxLength": 1000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Avaliação registrada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "`ValidationError` — campo desconhecido."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ImpersonationReadOnly`."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "409": {
            "description": "`AindaNaoResolvido`."
          },
          "422": {
            "description": "`ValidationError` — nota fora de 1..5."
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "nota 1..5 (o atendimento valida o intervalo); 409 `AindaNaoResolvido`.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.Avaliar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/suporte/chamados/{protocolo}/leitura": {
      "put": {
        "operationId": "SuporteMarcarChamadoLido",
        "x-asender-authz": "session",
        "tags": [
          "bff-suporte"
        ],
        "summary": "Marca as respostas da equipe como lidas.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "protocolo",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^CH-[0-9]{4}-[0-9]{6}$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Marcado."
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`ImpersonationReadOnly`."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "502": {
            "description": "atendimento indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "marca as respostas da equipe como lidas (204). Sem corpo.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.MarcarLida",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/suppressions": {
      "get": {
        "operationId": "BffListSuppressions",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Supressões da conta e as globais que afetam seus envios (ADR-0153 D7).",
        "description": "Sessão. `{id}` autorizado por pertinência (alheia = 404) e papel `owner`/`admin` (deny-default; demais 403,\nsem hop ao core). Só `state`, `origin`, `q`, `cursor`, `limit` viajam (valor até 256 caracteres, 400 acima). corpo e status do core voltam como vieram.",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ativa",
                "expirada",
                "removida"
              ]
            }
          },
          {
            "name": "origin",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 256
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{itens: SupressaoConta[], proximo_cursor?, globais: SupressaoEntrada[]}` (globais só sem cursor)."
          },
          "400": {
            "description": "valor de filtro longo demais"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`AdminRole` — não é owner nem admin."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "502": {
            "description": "core indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ListarConta",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/suppressions/{sid}": {
      "get": {
        "operationId": "BffGetSuppression",
        "x-asender-authz": "session",
        "tags": [
          "bff-tenants"
        ],
        "summary": "Detalhe de uma supressão da conta com a trilha (owner/admin).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platform_TenantPathId"
          },
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{entrada, eventos[]}`."
          },
          "400": {
            "description": "sid inválido"
          },
          "401": {
            "$ref": "#/components/responses/platform_Unauthorized"
          },
          "403": {
            "description": "`AdminRole`."
          },
          "404": {
            "$ref": "#/components/responses/platform_NotFoundOrDenied"
          },
          "502": {
            "description": "core indisponível"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "Onde é usada: detalhe na tela de supressões da conta.\n\nEfeitos: uma leitura ao core com o ator.",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.DetalheConta",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/api/tenants/{id}/supressao-global/consulta": {
      "get": {
        "operationId": "ConsultarSupressaoGlobalDaConta",
        "x-asender-authz": "session",
        "tags": [
          "bff-plataforma"
        ],
        "summary": "A conta consulta se um e-mail está em supressão global (só leitura).",
        "security": [
          {
            "SessionBearer": []
          },
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 320
            }
          }
        ],
        "responses": {
          "200": {
            "description": "suprimido, desde quando, origem e teste_disponivel_em"
          },
          "400": {
            "description": "e-mail inválido"
          },
          "404": {
            "description": "conta alheia"
          }
        },
        "servers": [
          {
            "url": "https://api.asender.net",
            "description": "Plataforma"
          }
        ],
        "description": "prova a pertinência à conta (alheia = 404), valida o e-mail e repassa a consulta ao core com o ator e o tenant. Só leitura — a conta abre chamado para remover.\n\nOnde é usada: selo \"Supressão global\" dos fronts de conta (mail e e-mail marketing).",
        "x-asender-servico": "asender-api",
        "x-asender-handler": "h.ConsultaConta",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/autoconfig/mail/config-v1.1.xml": {
      "get": {
        "tags": [
          "Domínios"
        ],
        "summary": "Configuração automática do Thunderbird (autoconfig, config-v1.1) do domínio",
        "description": "Pelo host da plataforma, com `?emailaddress=`. Pública e sem sessão: devolve o XML Mozilla (IMAP 993 SSL/TLS, SMTP 465 SSL/TLS, senha de aplicativo) SÓ para domínio verificado; domínio inexistente, não verificado ou host sem relação respondem o mesmo 404 (sem oráculo). Limite de 60 pedidos por minuto por IP.",
        "security": [],
        "parameters": [
          {
            "name": "emailaddress",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "o endereço digitado no cliente (alternativa ao Host `autoconfig.<domínio>`)"
          }
        ],
        "responses": {
          "200": {
            "description": "XML clientConfig 1.1",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "domínio não verificado ou desconhecido (corpo idêntico)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "429": {
            "description": "muitos pedidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "503": {
            "description": "consulta de domínio indisponível",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "a.servir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mail/config-v1.1.xml": {
      "get": {
        "tags": [
          "Domínios"
        ],
        "summary": "Configuração automática do Thunderbird (autoconfig, config-v1.1) do domínio",
        "description": "Pelo Host `autoconfig.<domínio>`, que é o que o Thunderbird chama. Pública e sem sessão: devolve o XML Mozilla (IMAP 993 SSL/TLS, SMTP 465 SSL/TLS, senha de aplicativo) SÓ para domínio verificado; domínio inexistente, não verificado ou host sem relação respondem o mesmo 404 (sem oráculo). Limite de 60 pedidos por minuto por IP.",
        "security": [],
        "parameters": [
          {
            "name": "emailaddress",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "o endereço digitado no cliente (alternativa ao Host `autoconfig.<domínio>`)"
          }
        ],
        "responses": {
          "200": {
            "description": "XML clientConfig 1.1",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "domínio não verificado ou desconhecido (corpo idêntico)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "429": {
            "description": "muitos pedidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "503": {
            "description": "consulta de domínio indisponível",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "a.servir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}/quarantine/release": {
      "post": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Liberar uma mensagem da Quarentena para a Entrada (ADR-0087)",
        "description": "Achado heurístico/PUA (`Heuristics.*`, `PUA.*`): quem tem a caixa libera. Achado de ASSINATURA (descoberto no reexame): só o admin (403 para os demais). Exige `{\"confirmo\": true}`. A liberação é auditada (quem, quando, qual assinatura) e NÃO muda o veredito: a mensagem continua `infectado`, e o download dos anexos exige `aceito_risco=1`. O PATCH de pasta recusa sair da Quarentena (409) — esta é a única saída.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "msgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirmo"
                ],
                "properties": {
                  "confirmo": {
                    "const": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "liberada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pasta": {
                      "const": "inbox"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "sem a confirmação",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "403": {
            "description": "achado de assinatura e quem pede não é admin",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "409": {
            "description": "a mensagem não está na Quarentena",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.LiberarDaQuarentena",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}/rescan": {
      "post": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Reexaminar agora uma mensagem pendente ou não verificada (ADR-0087)",
        "description": "Roda o antivírus sobre o `.eml` guardado e grava o veredito; malware vai para a Quarentena (heurística conforme a política da org). Teto de 10 por minuto por caixa (429). Scanner fora → 503 e a mensagem continua `pendente` (o reexame automático tenta de novo). Mensagem já verificada (`limpo`/`infectado`) ou sem o original guardado → 409.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "msgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "veredito",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_VereditoAV"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "409": {
            "description": "já verificada",
            "ou sem original guardado": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "429": {
            "description": "teto de 10 reexames por minuto na caixa (Retry-After)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "503": {
            "description": "antivírus desligado neste ambiente ou indisponível (Retry-After)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ReexaminarMensagem",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/labels": {
      "post": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Aplicar/remover etiquetas em lote (até 500 mensagens × 20 etiquetas)",
        "description": "Posse dupla por caixa na própria consulta: id de outra caixa é ignorado e não conta. aplicado_por = usuario:<id> com sessão.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_LoteDeEtiquetas"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "feito",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_ResultadoDeEtiquetas"
                }
              }
            }
          },
          "400": {
            "description": "lote vazio, grande demais, id malformado ou a mesma etiqueta nos dois lados"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.EtiquetarMensagensEmLote",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/threads/{threadId}/labels": {
      "post": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Aplicar/remover etiquetas em todas as mensagens de uma conversa",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "threadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_LoteDeEtiquetas"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "feito",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_ResultadoDeEtiquetas"
                }
              }
            }
          },
          "400": {
            "description": "pedido malformado"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "igual ao lote, com as mensagens escolhidas pela conversa da caixa.\n\nOnde é usada: POST /mailboxes/{id}/threads/{threadId}/labels (leitura em conversa).\n\nSaídas: 200 {vinculadas, desvinculadas}; 400; 404 (caixa ou conversa alheia).",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.EtiquetarConversa",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/labels/resumo": {
      "get": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Etiquetas da caixa com origem e contagens (a lateral)",
        "description": "GET /mailboxes/{id}/labels continua compatível; esta rota acrescenta origem/sistema, total e não lidas.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "labels": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/mail_EtiquetaComContagem"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ResumoDeEtiquetas",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/parecidos": {
      "post": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Filtrar parecidos — o que as mensagens escolhidas têm em comum, como critérios de busca",
        "description": "Critérios ordenados por cobertura (quantas das escolhidas pegam) e especificidade: remetente exato, conjunto de remetentes, padrão do endereço (`de:no-reply@`), domínio(s), domínio registrável, List-Id, começo/palavras comuns do assunto, destinatário, Cc, anexo; Reply-To aparece sem `busca` (não é campo de busca), com `motivo`. `sugerido` marca a combinação pré-marcada e `busca_sugerida` a junta. `total` é quantas mensagens da caixa a busca do critério acha. Ids de outra caixa são ignorados.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "critérios",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Parecidos"
                }
              }
            }
          },
          "400": {
            "description": "sem ids",
            "mais de 500 ou id malformado": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.FiltrarParecidos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/tipo": {
      "put": {
        "tags": [
          "Domínios"
        ],
        "summary": "Mudar o tipo da caixa (pessoal ↔ coletiva)",
        "description": "Só uma PESSOA admin do e-mail (sessão com usuário e papel `mail:admin`); API key e serviço sem usuário = 403. `coletiva` só para caixa de origem `manual`/`legado`/`automatica` (esta por promoção explícita do admin, emenda 2026-10-26 da ADR-0088); `conta_iam` = 422 `origem_conta_iam`. Voltar a `pessoal` com assinatura ativa = 422 `CaixaAssinada`. A origem nunca muda. Só caixa `coletiva` pode ser ligada a um assinante (ADR-0088 D4).",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tipo"
                ],
                "additionalProperties": false,
                "properties": {
                  "tipo": {
                    "type": "string",
                    "enum": [
                      "pessoal",
                      "coletiva"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "caixa atualizada (com tipo e origem)"
          },
          "400": {
            "description": "tipo inválido"
          },
          "403": {
            "description": "não é uma pessoa admin do e-mail"
          },
          "404": {
            "description": "caixa inexistente ou de outra conta"
          },
          "422": {
            "description": "inelegível ou assinada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_RecusaAssinatura"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.MudarTipoDaCaixa",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}": {
      "delete": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "exige administrador e posse do tenant (404 para outro tenant ou caixa já removida, sem vazar existência). Com vínculos (catch-all, encaminhamento/regra de outra caixa que aponta para ela, assinatura do atendimento) recusa com 409 `CaixaComVinculos` + `vinculos` — NÃO limpa o roteamento alheio em silêncio. Com sucesso a caixa deixa de receber (rejeita como inexistente), de autenticar no IMAP/SMTP, some das listas e do \"De\"; nenhuma mensagem, anexo ou blob é tocado.",
        "description": "exige administrador e posse do tenant (404 para outro tenant ou caixa já removida, sem vazar existência). Com vínculos (catch-all, encaminhamento/regra de outra caixa que aponta para ela, assinatura do atendimento) recusa com 409 `CaixaComVinculos` + `vinculos` — NÃO limpa o roteamento alheio em silêncio. Com sucesso a caixa deixa de receber (rejeita como inexistente), de autenticar no IMAP/SMTP, some das listas e do \"De\"; nenhuma mensagem, anexo ou blob é tocado.\n\nOnde é usada: DELETE /mailboxes/{id}.\n\nSaídas: 200 {removed, address}; 403; 404; 409.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.RemoverMailbox",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/restaurar": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "exige administrador; acha a removida (404 se ativa/inexistente/outro tenant); 409 `EnderecoEmUso` se uma caixa ativa já tomou o endereço e 409 `DominioRemovido` se o domínio saiu da conta. As mensagens retidas na quarentena para o endereço são ENTREGUES na caixa (idempotente) e a ação é auditada (ator).",
        "description": "exige administrador; acha a removida (404 se ativa/inexistente/outro tenant); 409 `EnderecoEmUso` se uma caixa ativa já tomou o endereço e 409 `DominioRemovido` se o domínio saiu da conta. As mensagens retidas na quarentena para o endereço são ENTREGUES na caixa (idempotente) e a ação é auditada (ator).\n\nOnde é usada: POST /mailboxes/{id}/restaurar.\n\nSaídas: 200 {restored, mailbox, retidas_entregues, retidas_pendentes?}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.RestaurarMailbox",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/aliases": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "ListarAliasesDaCaixa lista os alias ativos que entregam numa caixa do tenant.",
        "description": "Onde é usada: GET /mailboxes/{id}/aliases.\n\nSaídas: 200 {aliases, limite_por_caixa}; 403; 404.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarAliasesDaCaixa",
        "x-asender-fonte": "codigo"
      }
    },
    "/aliases/{id}": {
      "delete": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "admin do mail do tenant do alias (outro tenant = 404); corpo opcional {motivo}. A linha, o ator e o motivo ficam (auditoria); o endereço volta a ficar livre.",
        "description": "admin do mail do tenant do alias (outro tenant = 404); corpo opcional {motivo}. A linha, o ator e o motivo ficam (auditoria); o endereço volta a ficar livre.\n\nOnde é usada: DELETE /aliases/{id}.\n\nSaídas: 200 {removed, alias}; 403; 404.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ApagarAlias",
        "x-asender-fonte": "codigo"
      }
    },
    "/aliases/{id}/restaurar": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "409 se o endereço foi tomado (outro alias ou caixa), 422 se a caixa-destino sumiu ou foi assinada.",
        "description": "409 se o endereço foi tomado (outro alias ou caixa), 422 se a caixa-destino sumiu ou foi assinada.\n\nOnde é usada: POST /aliases/{id}/restaurar.\n\nSaídas: 200 {restored, alias}; 403; 404; 409; 422.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.RestaurarAlias",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/send": {
      "post": {
        "tags": [
          "Envio"
        ],
        "summary": "Enviar a partir de uma caixa específica",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Email"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "enviado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_EmailAceito"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/mail_NaoAutorizado"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "422": {
            "description": "malware num anexo (`AnexoInfectado`); nada foi enviado (ADR-0087)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "503": {
            "description": "mensagem com anexo e antivírus fora (`AntivirusIndisponivel`, Retry-After); o rascunho fica",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "confere posse (caixa -> domínio -> org -> tenant), monta o pedido, assina com a chave do domínio e entrega pelo relay; grava o outbound_log.\n\nOnde é usada: POST /mailboxes/{id}/send. Fluxo: JSON -> outbound.Service.Enviar (assina d=domínio) -> relay /raw.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.EnviarDeMailbox",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/signature": {
      "get": {
        "tags": [
          "Assinatura"
        ],
        "summary": "Ler a assinatura da caixa",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Assinatura"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz em cadeia (caixa -> domínio -> tenant) + a coluna `signature_html`.\n\nOnde é usada: GET /mailboxes/{id}/signature — a aba Assinatura das configurações e o compositor, que a injeta numa composição nova.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.LerAssinatura",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Assinatura"
        ],
        "summary": "Gravar a assinatura da caixa (sanitizada no servidor)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Assinatura"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "gravada"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz em cadeia + sanitização (anti-XSS) + UPDATE.\n\nOnde é usada: PUT /mailboxes/{id}/signature, corpo {\"signature_html\": \"...\"}. Devolve a assinatura COMO FICOU GRAVADA (já sanitizada), para a tela mostrar exatamente o que passou a valer — e não o que a pessoa digitou.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.GravarAssinatura",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/drafts": {
      "post": {
        "tags": [
          "Rascunhos"
        ],
        "summary": "Criar rascunho (auto-save do compositor)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Rascunho"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criado"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz da caixa (exigirCaixa) + persistência na pasta drafts. Devolve o rascunho com o id, que o compositor guarda para os PUTs seguintes do auto-save.\n\nOnde é usada: POST /mailboxes/{id}/drafts.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CriarRascunho",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/drafts/{draftId}": {
      "get": {
        "tags": [
          "Rascunhos"
        ],
        "summary": "Retomar rascunho",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz da caixa + AcharRascunho.\n\nOnde é usada: GET /mailboxes/{id}/drafts/{draftId}, disparado ao abrir um item da pasta Rascunhos. Não-rascunho/outra caixa -> 404.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.RetomarRascunho",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Rascunhos"
        ],
        "summary": "Atualizar rascunho",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Rascunho"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz da caixa + UPDATE escopado ao rascunho. É a rota que o auto-save bate a cada ~2s. Rascunho de outra caixa ou id que não é rascunho -> 404.\n\nOnde é usada: PUT /mailboxes/{id}/drafts/{draftId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.AtualizarRascunho",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Rascunhos"
        ],
        "summary": "Descartar rascunho",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "descartado"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz da caixa + ApagarRascunho (idempotente). É o descarte à mão e o passo final do envio (o compositor apaga o rascunho depois de enviar). Idempotente: apagar duas vezes devolve 200 nas duas — o cliente não precisa saber se o rascunho já tinha sumido.\n\nOnde é usada: DELETE /mailboxes/{id}/drafts/{draftId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.DescartarRascunho",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/contacts": {
      "get": {
        "tags": [
          "Contatos"
        ],
        "summary": "Listar a agenda da caixa",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/mail_Contato"
                  }
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: GET /mailboxes/{id}/contacts. `q` casa nome OU e-mail (substring, case-insensitive); ausente devolve tudo.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarContatos",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Contatos"
        ],
        "summary": "Adicionar contato à agenda da caixa",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Contato"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criado"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: POST /mailboxes/{id}/contacts. E-mail repetido na caixa -> 409.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CriarContato",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/contacts/{contatoId}": {
      "patch": {
        "tags": [
          "Contatos"
        ],
        "summary": "Atualizar contato",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "contatoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Contato"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: PATCH /mailboxes/{id}/contacts/{contatoId}. Contato de outra caixa (ou id que não é contato) -> 404; novo e-mail que colide -> 409.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.AtualizarContato",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Contatos"
        ],
        "summary": "Apagar contato",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "contatoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "apagado"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: DELETE /mailboxes/{id}/contacts/{contatoId}. Contato inexistente ou de outra caixa -> 404 (o mesmo da leitura; não deixa sondar contato alheio).",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ApagarContato",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/recipients/suggest": {
      "get": {
        "tags": [
          "Contatos"
        ],
        "summary": "Autocomplete de destinatários (catálogo + histórico)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "prefixo digitado"
          }
        ],
        "responses": {
          "200": {
            "description": "sugestões"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: GET /mailboxes/{id}/recipients/suggest?q=. Combina o catálogo e o histórico de mensagens da caixa (ver store.SugerirDestinatarios).",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.SugerirDestinatarios",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/resumo": {
      "get": {
        "tags": [
          "Resumo"
        ],
        "summary": "Resumo da ferramenta para o hub e a home (contrato comum de resumo).",
        "description": "Visitas (sessões), visualizações, conversões de formulário e taxa de conversão — com a janela anterior —, páginas publicadas (no ar agora) e a série de visitas por dia. Visitas e conversões vêm do runtime; com ele fora do ar, esses indicadores e a série SOMEM (nunca viram zero) e o cartão segue com as páginas publicadas. Tenant e usuário vêm da credencial verificada, nunca da query. Conta alheia → 404; membro sem acesso ao produto → 403.",
        "parameters": [
          {
            "name": "periodo",
            "in": "query",
            "required": false,
            "description": "janela dos indicadores — os N últimos dias fechados em hoje (UTC). Padrão 30d.",
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "o resumo da ferramenta `pages`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ResumoDeFerramenta"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/pages_PedidoInvalidoAutoria"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "membro da conta sem acesso a esta ferramenta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Obter",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages": {
      "get": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Listar mensagens da caixa (por pasta, marcadores e busca)",
        "description": "Paginada por cursor opaco. O escopo é sempre a caixa da rota — a query recorta, nunca amplia. Cursor inválido/velho volta à primeira página em vez de virar erro.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "folder",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "kind (inbox/sent/archive/spam/trash/drafts) ou uuid da pasta; ausente = todas"
          },
          {
            "name": "unread",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true",
                "sim"
              ]
            },
            "description": "só não-lidas"
          },
          {
            "name": "starred",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true",
                "sim"
              ]
            },
            "description": "só estreladas"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 512
            },
            "description": "Linguagem de busca (estilo Gmail). Espaço = E; `OR` (ou vírgula dentro de um operador) = OU; `-` nega. Operadores: `de:`/`from:`, `para:`/`to:`, `cc:`, `assunto:`/`subject:`, `dominio:` (domínio do remetente OU subdomínio dele), `tem:anexo`, `etiqueta:\"Nome\"`, `pasta:` (entrada, arquivo, enviados, rascunhos, spam, lixeira ou o nome de uma pasta sua), `antes:`/`depois:` (AAAA-MM-DD, UTC), `lista:` (List-Id exato), `maior:`/`menor:` (tamanho do .eml: 5MB, 200KB, 1.5MB ou bytes; não medido não casa), `lida:sim|nao`, `is:read|unread`, `nao-lida`. `pasta:todas` = todo o correio menos Rascunhos/Spam/Lixeira/Quarentena. `campo:(a OR b)` = `campo:a,b`. Valor de de:/para: com `@` no meio = endereço exato; com cara de domínio = domínio/subdomínio; outro = trecho. Palavra solta com cara de domínio/endereço (`anthropic.com`) = remetente; outra palavra = texto livre (assunto, remetente, corpo; com radicais); entre aspas = sempre texto. Limites: 512 caracteres, 30 valores, 20 por OU, 5 etiquetas; 400 BuscaInvalida com a frase. Ex.: `anthropic.com,claude.com,claude.ai` ou `anthropic.com OR claude.com OR claude.ai`."
          },
          {
            "name": "label",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "id de uma etiqueta; só as mensagens que a carregam (eixo ortogonal à pasta)"
          },
          {
            "name": "todas",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true",
                "sim"
              ]
            },
            "description": "Todos os e-mails: todas as pastas menos Rascunhos (e menos Spam/Lixeira/Quarentena sem `ocultas`); ignora `folder`; a busca vale dentro"
          },
          {
            "name": "ocultas",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true",
                "sim"
              ]
            },
            "description": "com `todas`: inclui Spam, Lixeira e Quarentena"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "o next_cursor da página anterior (opaco)"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_PaginaDeMensagens"
                }
              }
            }
          },
          "400": {
            "description": "busca inválida (BuscaInvalida",
            "com a frase)": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarMensagens",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}": {
      "get": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Ler a mensagem completa (marca como lida)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "msgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz em cadeia (caixa->domínio->tenant) + AcharMensagem + MarcarLido.\n\nOnde é usada: GET /mailboxes/{id}/messages/{msgId}. Cross-tenant/outra caixa -> 404.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.GetMensagem",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Marcar lida, estrelar ou mover uma mensagem",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "msgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_PatchDeMensagem"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "aplicado"
          },
          "400": {
            "description": "corpo sem nenhum campo"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "409": {
            "description": "mover PARA a Quarentena, ou tirar dela por aqui — a saída é POST …/quarantine/release (ADR-0087)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mail_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz em cadeia + UPDATE escopado por (caixa, id).\n\nOnde é usada: PATCH /mailboxes/{id}/messages/{msgId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.AtualizarMensagem",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}/attachments/{attId}": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "caixa->domínio->tenant + AcharAnexo (scopeado à mensagem/caixa); responde com o conteúdo e Content-Disposition.",
        "description": "caixa->domínio->tenant + AcharAnexo (scopeado à mensagem/caixa); responde com o conteúdo e Content-Disposition.\n\nOnde é usada: GET /mailboxes/{id}/messages/{msgId}/attachments/{attId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.GetAnexo",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}/raw": {
      "get": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Baixar o .eml original (ADR-0028)",
        "description": "O bruto guardado sai byte a byte como chegou; à FRENTE dele, numa mensagem recebida, vão cabeçalhos SINTETIZADOS do veredito gravado (ADR-0087): `X-Spam-Flag` (YES quando a mensagem está hoje na pasta Spam), `X-Spam-Status`, `X-Spam-Score`, `X-Spam-Action` e `X-Spam-Symbols` (quando o motor de conteúdo opinou) e o `Authentication-Results` que geramos. Cabeçalhos acrescentados no topo não invalidam a assinatura DKIM.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "msgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "message/rfc822"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.GetRaw",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/threads/{threadId}": {
      "get": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Conversa agrupada (threading, M-C)",
        "description": "Todas as mensagens da thread, em ordem cronológica (mais antiga primeiro), com as imagens inline (cid:) já embutidas como data:. Escopo por caixa: a thread de outra caixa não é alcançável nem com o uuid.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "threadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.GetThread",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/labels": {
      "get": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Listar etiquetas da caixa (M-E)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: GET /mailboxes/{id}/labels.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarLabels",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Criar etiqueta na caixa",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Etiqueta"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criada"
          },
          "400": {
            "description": "nome inválido"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "409": {
            "description": "nome já existe na caixa"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: POST /mailboxes/{id}/labels.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CriarLabel",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/labels/{labelId}": {
      "patch": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Renomear/recolorir uma etiqueta",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "labelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Etiqueta"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "409": {
            "description": "nome já existe na caixa, ou etiqueta de sistema (só a cor muda)"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: PATCH /mailboxes/{id}/labels/{labelId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.AtualizarLabel",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Apagar uma etiqueta (as mensagens perdem só o vínculo)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "labelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "409": {
            "description": "etiqueta de sistema não se apaga"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: DELETE /mailboxes/{id}/labels/{labelId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ApagarLabel",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}/labels": {
      "post": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Aplicar uma etiqueta a uma mensagem (idempotente)",
        "description": "Etiqueta e mensagem têm de ser da caixa; caso contrário 404 (anti cross-caixa).",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "msgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "label_id"
                ],
                "properties": {
                  "label_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "aplicado"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.AplicarLabelEmMensagem",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}/labels/{labelId}": {
      "delete": {
        "tags": [
          "Etiquetas"
        ],
        "summary": "Remover uma etiqueta de uma mensagem (idempotente)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "msgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "labelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "removido"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: DELETE /mailboxes/{id}/messages/{msgId}/labels/{labelId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.RemoverLabelDeMensagem",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/vacation": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "LerFerias devolve a regra de férias da caixa (o padrão quando não há).",
        "description": "Onde é usada: GET /mailboxes/{id}/vacation.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.LerFerias",
        "x-asender-fonte": "codigo"
      },
      "put": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "valida a janela e faz UPSERT.",
        "description": "valida a janela e faz UPSERT.\n\nOnde é usada: PUT /mailboxes/{id}/vacation. A auto-resposta em si é um ENVIO no recebimento e passa pelo guard do piso 18; aqui só se guarda a REGRA.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.GravarFerias",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/forwarding": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "LerForward devolve a regra de encaminhamento da caixa.",
        "description": "Onde é usada: GET /mailboxes/{id}/forwarding.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.LerForward",
        "x-asender-fonte": "codigo"
      },
      "put": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "valida o destino (net/mail, no store) e faz UPSERT.",
        "description": "valida o destino (net/mail, no store) e faz UPSERT.\n\nOnde é usada: PUT /mailboxes/{id}/forwarding. Ligar sem destino é recusado (400). O encaminhamento em si é um ENVIO no recebimento (guard do piso 18).",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.GravarForward",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/pgp/keys": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "ListarChavesPGP devolve as chaves públicas da caixa (fingerprint + uid, sem o",
        "description": "Onde é usada: GET /mailboxes/{id}/pgp/keys.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarChavesPGP",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "valida o bloco armored no servidor (é chave PÚBLICA? qual a fingerprint?), deriva fingerprint/uid e faz UPSERT por (caixa, endereço).",
        "description": "valida o bloco armored no servidor (é chave PÚBLICA? qual a fingerprint?), deriva fingerprint/uid e faz UPSERT por (caixa, endereço).\n\nOnde é usada: POST /mailboxes/{id}/pgp/keys {email, public_key, is_own}. A fingerprint devolvida é a DERIVADA aqui — a pessoa a confere fora de banda.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CadastrarChavePGP",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/pgp/keys/{keyId}": {
      "delete": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "ApagarChavePGP remove uma chave da caixa.",
        "description": "Onde é usada: DELETE .../pgp/keys/{keyId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ApagarChavePGP",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}/pgp-verify": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "carrega a mensagem (da caixa), acha a chave do From e verifica o clearsign do corpo.",
        "description": "carrega a mensagem (da caixa), acha a chave do From e verifica o clearsign do corpo.\n\nOnde é usada: POST /mailboxes/{id}/messages/{msgId}/pgp-verify.\n\nSaídas: {verified, signer_fingerprint} — `verified:false` (200) quando não há chave do remetente ou a assinatura não confere; a tela mostra \"não verificado\" em vez de tratar como erro de sistema.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.VerificarPGP",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/import": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "decodifica o arquivo, separa nas mensagens, e grava cada uma que a caixa ainda não tem (por Message-ID).",
        "description": "decodifica o arquivo, separa nas mensagens, e grava cada uma que a caixa ainda não tem (por Message-ID).\n\nOnde é usada: POST /mailboxes/{id}/import.\n\nSaídas: {imported, skipped, total}. Uma mensagem que falha ao gravar é contada em `errors` e NÃO derruba o import inteiro — o resto é aproveitado.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ImportarMensagens",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/import/remote/test": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "TestarImportacaoRemota conecta no provedor, autentica e lista as pastas. NÃO persiste nada.",
        "description": "Onde é usada: POST /mailboxes/{id}/import/remote/test. Respostas: 200 {folders:[{name,messages,special_use}]}; 400 ValidationError; 422 DestinoBloqueado|AutenticacaoFalhou|CertificadoInvalido; 429; 502 ProvedorIndisponivel.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.TestarImportacaoRemota",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/import/remote": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "CriarImportacaoRemota cifra a senha e enfileira o job.",
        "description": "Onde é usada: POST /mailboxes/{id}/import/remote. Com `conexao_id` no corpo, host e senha vêm da conexão do cofre (conexoes_job.go); sem ele, são os digitados. Respostas: 202 {job_id}; 400 ValidationError; 409 ImportacaoEmAndamento (já há job ativo na caixa); 429. A mesma validação síncrona LEVE do teste (sem rede).",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CriarImportacaoRemota",
        "x-asender-fonte": "codigo"
      },
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "ListarImportacoesRemotas devolve os últimos 20 jobs da caixa.",
        "description": "Onde é usada: GET /mailboxes/{id}/import/remote.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarImportacoesRemotas",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/import/remote/{job_id}": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "LerImportacaoRemota devolve um job. Job de outra caixa ou outro tenant = 404.",
        "description": "Onde é usada: GET .../{job_id}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.LerImportacaoRemota",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/import/remote/{job_id}/cancel": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "CancelarImportacaoRemota cancela o job (zera a senha, audita). Idempotente em estado terminal.",
        "description": "Onde é usada: POST .../{job_id}/cancel. O runner percebe o status e para o motor; o já importado fica.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CancelarImportacaoRemota",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/conexoes": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "ListarConexoes devolve as conexões da caixa `?mailbox_id=`.",
        "description": "Onde é usada: GET /v1/conexoes. Respostas: 200 {conexoes:[…]} (sem segredo); 400 sem/inválido mailbox_id; 403; 404.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarConexoes",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "CriarConexao cifra o segredo e guarda a conexão.",
        "description": "Onde é usada: POST /v1/conexoes. Respostas: 201 (a conexão, sem segredo); 400 ValidationError; 403; 404; 409 LimiteDeConexoes; 429; 503.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CriarConexao",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/conexoes/{id}": {
      "patch": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "AlterarConexao aplica o PATCH.",
        "description": "Onde é usada: PATCH /v1/conexoes/{id}. `segredo` ausente/vazio mantém o segredo guardado; trocar servidor, porta, segurança ou usuário exige informá-lo de novo (400). Respostas: 200 (a conexão, sem segredo); 400; 403; 404; 429.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.AlterarConexao",
        "x-asender-fonte": "codigo"
      },
      "delete": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "RemoverConexao apaga a conexão e o segredo de verdade (exceção a \"nada se apaga\": é credencial). NÃO toca o",
        "description": "Onde é usada: DELETE /v1/conexoes/{id}. Respostas: 204; 403; 404; 429.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.RemoverConexao",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/conexoes/{id}/testar": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "TestarConexao conecta no provedor com a conexão guardada, autentica e lista as pastas, e grava o resultado",
        "description": "Onde é usada: POST /v1/conexoes/{id}/testar. Respostas: 200 {ok:true,folders,conexao} ou 200 {ok:false,error_code} (DestinoBloqueado|AutenticacaoFalhou|CertificadoInvalido|ProvedorIndisponivel|…); 429; 500.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.TestarConexao",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/identities": {
      "get": {
        "tags": [
          "Identidades"
        ],
        "summary": "Listar identidades de envio da caixa (M-E)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: GET /mailboxes/{id}/identities.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarIdentidades",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Identidades"
        ],
        "summary": "Criar identidade de envio (send-as)",
        "description": "O par nome+endereço que a caixa pode usar como \"De\". O endereço é aceito aqui, mas o ENVIO por ele só passa se o domínio for da org com DKIM verificado (a trava anti-spoofing está no envio).",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Identidade"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criada"
          },
          "400": {
            "description": "endereço inválido"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "409": {
            "description": "endereço já existe na caixa"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CriarIdentidade",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/identities/{identityId}": {
      "patch": {
        "tags": [
          "Identidades"
        ],
        "summary": "Editar uma identidade de envio",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "identityId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/mail_Identidade"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          },
          "409": {
            "description": "endereço já existe na caixa"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: PATCH /mailboxes/{id}/identities/{identityId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.AtualizarIdentidade",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Identidades"
        ],
        "summary": "Apagar uma identidade de envio",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          },
          {
            "name": "identityId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "Onde é usada: DELETE /mailboxes/{id}/identities/{identityId}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ApagarIdentidade",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/folders": {
      "get": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Pastas da caixa, com total e não-lidas",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "folders": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/mail_Pasta"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz em cadeia (caixa -> domínio -> tenant) + contadores numa consulta.\n\nOnde é usada: GET /mailboxes/{id}/folders, que desenha o rail.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarPastas",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Criar pasta do usuário (kind=custom)",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "responses": {
          "201": {
            "description": "criada"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "description": "authz por caixa + INSERT com `kind='custom'`.\n\nOnde é usada: POST /mailboxes/{id}/folders.\n\nSaídas: 201 com a pasta; 400/409 conforme o nome; 404 se a caixa não é de quem pediu.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.CriarPasta",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/folders/{fid}": {
      "patch": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "authz por caixa + UPDATE restrito a `custom`.",
        "description": "authz por caixa + UPDATE restrito a `custom`.\n\nOnde é usada: PATCH /mailboxes/{id}/folders/{fid}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.RenomearPasta",
        "x-asender-fonte": "codigo"
      },
      "delete": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "authz por caixa + DELETE restrito a `custom` e sem conteúdo.",
        "description": "authz por caixa + DELETE restrito a `custom` e sem conteúdo.\n\nOnde é usada: DELETE /mailboxes/{id}/folders/{fid}.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ApagarPasta",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/messages/batch": {
      "post": {
        "tags": [
          "Mensagens"
        ],
        "summary": "Aplicar a mesma alteração a várias mensagens",
        "description": "A autorização é por CAIXA; dentro dela cada id é conferido. Id que não pertence à caixa volta em `negados` (não é aplicado nem ignorado em silêncio). Máximo de 500 ids.",
        "parameters": [
          {
            "$ref": "#/components/parameters/mail_MailboxID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids",
                  "patch"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 500
                  },
                  "patch": {
                    "$ref": "#/components/schemas/mail_PatchDeMensagem"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "aplicado (possivelmente em parte)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "aplicados": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "negados": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "lista vazia, mais de 500, ou patch sem campo"
          },
          "404": {
            "$ref": "#/components/responses/mail_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.AplicarEmLote",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/mailboxes/{id}/messages/{msgId}/phishing": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "ver o comentário do arquivo.",
        "description": "ver o comentário do arquivo.\n\nOnde é usada: botão \"É golpe\"/\"Não é golpe\" do webmail.\n\nSaídas: 200 {ok, pasta, item_id?}; 400 corpo inválido; 404 caixa/mensagem alheia; 409 `nao_esta_em_phishing`; 429 teto diário; 503 `servico_indisponivel`.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.DenunciarPhishing",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/avatar": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "exigirCaixaOuGestao (tenant + grant, ou admin do mail: a foto é metadado de gestão; 404 para quem não pode) e os bytes com os cabeçalhos que impedem a imagem de virar documento na origem do webmail.",
        "description": "exigirCaixaOuGestao (tenant + grant, ou admin do mail: a foto é metadado de gestão; 404 para quem não pode) e os bytes com os cabeçalhos que impedem a imagem de virar documento na origem do webmail.\n\nOnde é usada: GET /mailboxes/{id}/avatar.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.LerAvatarDaCaixa",
        "x-asender-fonte": "codigo"
      },
      "put": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "só ADMIN do mail no tenant da caixa (exigirAdminNaCaixa); a imagem é decodificada e recodificada em PNG ≤ 256 px ANTES de tocar o blob.",
        "description": "só ADMIN do mail no tenant da caixa (exigirAdminNaCaixa); a imagem é decodificada e recodificada em PNG ≤ 256 px ANTES de tocar o blob.\n\nOnde é usada: PUT /mailboxes/{id}/avatar.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.EnviarAvatarDaCaixa",
        "x-asender-fonte": "codigo"
      },
      "delete": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "ApagarAvatarDaCaixa remove a foto.",
        "description": "Onde é usada: DELETE /mailboxes/{id}/avatar (admin).",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ApagarAvatarDaCaixa",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/lixeira": {
      "get": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "lista o que o usuário apagou na caixa, do mais recente ao mais antigo.",
        "description": "lista o que o usuário apagou na caixa, do mais recente ao mais antigo.\n\nOnde é usada: tela Configurações → Lixeira do webmail. Exige acesso à caixa (mesmo critério de apagar) e sessão de usuário.\n\nSaídas: 200 `{itens, cursor}`; 403 sem sessão de usuário; 404 caixa inacessível; 422 tipo/cursor inválido. Só leitura.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.ListarLixeiraDaCaixa",
        "x-asender-fonte": "codigo"
      }
    },
    "/mailboxes/{id}/lixeira/{tipo}/{itemId}/restaurar": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "devolve à vista do usuário um item que ELE apagou na caixa.",
        "description": "devolve à vista do usuário um item que ELE apagou na caixa.\n\nOnde é usada: botão Restaurar da tela de Lixeira.\n\nSaídas: 204; 403 sem sessão de usuário; 404 (não está apagado, é de outro usuário/caixa, ou tipo fora da allowlist); 409 `conflito_unicidade`.\n\nEfeitos: UPDATE com origem `usuario:<id>` e log do evento.",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.RestaurarDaLixeiraDaCaixa",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/lixeira/{tipo}/{id}/enviar-admin": {
      "post": {
        "servers": [
          {
            "url": "https://api.mail.asender.net",
            "description": "E-mail"
          }
        ],
        "tags": [
          "mail"
        ],
        "summary": "tira da vista do usuario SEM encurtar os 180d; Idempotency-Key OBRIGATORIA; nenhum parametro reduz o prazo;",
        "description": "tira da vista do usuario SEM encurtar os 180d; Idempotency-Key OBRIGATORIA; nenhum parametro reduz o prazo;\n\nOnde é usada: botao \"enviar para a administrativa\").",
        "x-asender-servico": "asender-mail",
        "x-asender-handler": "h.enviarAdminUsuario",
        "x-asender-fonte": "codigo"
      }
    },
    "/.well-known/openid-configuration": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Metadados do provedor OIDC (discovery)",
        "operationId": "discovery",
        "x-asender-authz": "public",
        "description": "Anuncia os endpoints e as capacidades do Authorization Server. É o primeiro request de um client: ele se autoconfigura a partir daqui, em vez de ter as URLs cravadas. Só `response_type=code`, só PKCE `S256`, só `RS256` — o perfil OAuth 2.1 da casa.",
        "security": [],
        "responses": {
          "200": {
            "description": "Os metadados do provedor.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "public, max-age=300"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_Discovery"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.Discovery",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/.well-known/jwks.json": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Chaves públicas (JWKS)",
        "operationId": "jwks",
        "x-asender-authz": "public",
        "description": "As chaves públicas RS256 do provedor. O client busca aqui para validar a assinatura do ID token (e de qualquer JWT deste AS), escolhendo a chave pelo `kid` do header do token. Cacheável; numa rotação, o JWKS traz a chave nova e a antiga convivem até os tokens velhos expirarem.",
        "security": [],
        "responses": {
          "200": {
            "description": "O JWK Set.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "public, max-age=300"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_JWKS"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.JWKS",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Metadados do Authorization Server (RFC 8414)",
        "operationId": "authServerMetadata",
        "x-asender-authz": "public",
        "description": "O MESMO documento de `/.well-known/openid-configuration` (uma fonte só, nunca divergem), servido no caminho do RFC 8414 para clientes OAuth/MCP que não falam OIDC. Anuncia `token_endpoint_auth_methods_supported` (`client_secret_basic`, `client_secret_post`, `none`) e `authorization_response_iss_parameter_supported`. Anuncia `registration_endpoint` (RFC 7591, `POST /register`) quando `OAUTH_DCR_ATIVO=true` e `client_id_metadata_document_supported: true` (CIMD) quando `OAUTH_CIMD_ATIVO=true` — cada um some do documento se desligado.",
        "security": [],
        "responses": {
          "200": {
            "description": "Os metadados do Authorization Server.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "public, max-age=300"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_Discovery"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.AuthServerMetadata",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/authorize": {
      "get": {
        "tags": [
          "OAuth 2.1"
        ],
        "summary": "Início do login (Authorization Code + PKCE)",
        "operationId": "authorize",
        "x-asender-authz": "session",
        "description": "O endpoint para onde o app manda o **browser** iniciar o login. Valida o `client_id` e a `redirect_uri` (allowlist EXATA do client) e exige PKCE `S256`. Se houver sessão viva no domínio de auth, emite um **authorization code** de uso único e devolve por `302` ao `redirect_uri` (`?code=…&state=…`). Sem sessão, redireciona para a tela de login e, após autenticar, volta para cá.\n\n**`client_id` em forma de URL (CIMD, ADR-0130 D5).** Se o `client_id` é uma URL `https` (porta 443, sem credencial nem fragmento), o AS busca o documento JSON de metadados naquela URL (só IP público; sem redirect; 5 s; ≤ 64 KiB; `client_id` do documento igual à URL), cria/atualiza o client (`origem=cimd`, público, **não verificado**, só escopos de leitura) e segue o fluxo. Cache de 5 min a 24 h (`Cache-Control: max-age`); se a rebusca falhar por rede, usa a cópia de até 7 dias. Documento inválido, destino interno ou domínio bloqueado => `400 invalid_client` (sem redirect). Muitas buscas do mesmo IP => `429`.\n\n**Apps de terceiro** (ADR-0128): o `scope` é validado contra o catálogo e contra o que o app pode pedir (`invalid_scope` por redirect); na 1ª vez o usuário passa pela tela de consentimento (`302` para `<authfront>/consentir?pedido=…`), onde escolhe UMA conta; nas seguintes, com sessão viva e um consentimento que cobre os escopos, o `code` sai na hora (o \"1 clique\"). `prompt=none` devolve `login_required` / `consent_required`; `prompt=consent` força a tela; `prompt=login` força o login. Toda resposta ao app de terceiro (code ou erro) traz `iss` (RFC 9207).\n**Recurso por audiência** (RFC 8707): `resource` (repetível) escolhe a(s) API(s) que o token vai acessar; cada valor tem de estar na allowlist `OIDC_RECURSOS_PERMITIDOS` (padrão `https://api.asender.net` e `https://api.asender.net/mcp`), senão `error=invalid_target` por redirect (com `state` e `iss`). Sem `resource` o `aud` é o recurso público padrão.\n\n**Loopback (RFC 8252 §7.3):** para client PÚBLICO, `redirect_uri` de loopback (`http://127.0.0.1`, `http://[::1]`, `http://localhost`) casa com a cadastrada ignorando só a porta; esquema, host, caminho e query seguem exatos. Confidencial continua exato.\n\nEste endpoint é uma navegação de browser, não uma chamada de API: a resposta normal é `302`. Erros só voltam por `302` com `?error=` DEPOIS que a `redirect_uri` foi validada; antes disso (client/redirect desconhecidos) a falha é mostrada, não redirecionada — devolver a uma URI não registrada seria open-redirect.",
        "security": [],
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            },
            "description": "único valor aceito (OAuth 2.1: sem implicit)"
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "id do client OAuth registrado"
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "tem de casar EXATAMENTE com uma URI registrada no client"
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "openid profile email offline_access"
              ]
            },
            "description": "lista separada por espaço; `openid` é sempre garantido; `offline_access` pede refresh token"
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "opaco, devolvido intacto; use para amarrar a resposta ao request e barrar CSRF"
          },
          {
            "name": "nonce",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "ligado ao ID token (anti-replay); o client confere que o `nonce` do id_token é o que enviou"
          },
          {
            "name": "prompt",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "examples": [
                "none",
                "consent",
                "login"
              ]
            },
            "description": "só apps de terceiro: `none` (sem tela; `login_required`/`consent_required`), `consent` (força a tela), `login` (força o login). `none` não combina com outros"
          },
          {
            "name": "resource",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uri"
              }
            },
            "description": "RFC 8707, repetível, só apps de terceiro: recurso(s) do token; fora da allowlist => `invalid_target`"
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "PKCE: base64url(SHA-256(code_verifier))"
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            },
            "description": "único método aceito"
          }
        ],
        "responses": {
          "302": {
            "description": "Redireciona ao `redirect_uri` com `code` e `state` (sucesso) ou com `error`/`error_description` e `state` (falha após validar o redirect); ou à tela de login quando não há sessão.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "400": {
            "description": "`client_id` desconhecido ou `redirect_uri` não registrada — a falha é mostrada aqui, não redirecionada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_ErroOAuth"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.Authorize",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/token": {
      "post": {
        "tags": [
          "OAuth 2.1"
        ],
        "summary": "Troca de code/refresh por tokens",
        "operationId": "token",
        "x-asender-authz": "public",
        "description": "Troca o `authorization_code` por tokens (provando o PKCE `code_verifier`) ou rotaciona um `refresh_token`. Chamado servidor-a-servidor (ou pelo BFF), com corpo `application/x-www-form-urlencoded`.\n\n**Autenticação do client.** Client público (SPA/mobile) não tem segredo — o PKCE prova a posse. Client confidencial autentica por HTTP Basic (`client_secret_basic`) ou pelos campos `client_id`/`client_secret` do corpo.\n\n**Apps de terceiro** (ADR-0128): client confidencial (segredo) ou **público** (sem segredo; PKCE; mandar `client_secret` => `invalid_client`; refresh de no máximo 30 dias). O access token sai com `aud` = os recursos concedidos (RFC 8707; sem `resource`, a API pública `OIDC_RECURSO_PUBLICO`), `azp` = o client, `tenant` = a conta do consentimento, `scope` = o escopo EFETIVO (pedido ∩ permitido no app ∩ concedido), `ext: true` (nunca vale como sessão) e `jti`; vive 15 min. O refresh é preso ao **grant** (não à sessão do navegador), vive o TTL do app e é revogado quando o usuário remove o app. Grant revogado ou usuário sem acesso à conta → `invalid_grant`.\n\n**Rotação de refresh com detecção de reuso.** Cada uso do refresh o invalida e emite um novo. Reapresentar um refresh já usado revoga a família inteira e responde `invalid_grant` — o sinal de token roubado.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "authorization_code",
                      "refresh_token"
                    ]
                  },
                  "code": {
                    "type": "string",
                    "description": "grant `authorization_code`: o code recebido no `/authorize`"
                  },
                  "redirect_uri": {
                    "type": "string",
                    "format": "uri",
                    "description": "grant `authorization_code`: a MESMA `redirect_uri` do `/authorize` (anti code-injection)"
                  },
                  "code_verifier": {
                    "type": "string",
                    "description": "grant `authorization_code`: o verifier do PKCE cujo SHA-256 é o `code_challenge`"
                  },
                  "refresh_token": {
                    "type": "string",
                    "description": "grant `refresh_token`: o refresh a rotacionar"
                  },
                  "resource": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri"
                    },
                    "description": "RFC 8707, repetível, só apps de terceiro: tem de ser SUBCONJUNTO do que o code/refresh concedeu (só estreita); senão `invalid_target`. Sem ele, `aud` = o concedido"
                  },
                  "client_id": {
                    "type": "string",
                    "description": "id do client (se não vier por HTTP Basic)"
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "só client confidencial (se não vier por HTTP Basic)"
                  }
                }
              },
              "examples": {
                "authorization_code": {
                  "summary": "Trocar o code por tokens (client público + PKCE)",
                  "value": {
                    "grant_type": "authorization_code",
                    "code": "cod_9f8e7d…",
                    "redirect_uri": "https://app.exemplo.com.br/callback",
                    "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
                    "client_id": "app-exemplo"
                  }
                },
                "refresh_token": {
                  "summary": "Rotacionar o refresh",
                  "value": {
                    "grant_type": "refresh_token",
                    "refresh_token": "rft_1a2b3c…",
                    "client_id": "app-exemplo"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tokens emitidos.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_grant` (code/refresh inválido, expirado, já usado, ou redirect_uri/PKCE que não conferem; refresh reusado → família revogada; refresh de SESSÃO encerrada — revogada ou expirada — → família revogada, \"sessão encerrada\"), `invalid_request` (corpo inválido) ou `unsupported_grant_type`, ou `invalid_target` (apps de terceiro: `resource` fora do concedido ou da allowlist).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_ErroOAuth"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_client` — client não autenticado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_ErroOAuth"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.Token",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/register": {
      "post": {
        "tags": [
          "OAuth 2.1"
        ],
        "summary": "Registro dinâmico de client (RFC 7591)",
        "operationId": "oauthRegister",
        "x-asender-authz": "public",
        "security": [],
        "description": "Registra um client **público** sem cadastro prévio (clientes MCP como Claude, ChatGPT e Cursor que ainda não fazem CIMD). Corpo `application/json` (≤ 64 KiB) com os metadados do RFC 7591: `redirect_uris` (obrigatório; `https` ou `http` em `127.0.0.1`, `localhost`, `[::1]`, sem credencial nem fragmento, até 10), `client_name`, `client_uri`, `logo_uri` (https), `grant_types` (⊆ `authorization_code`, `refresh_token`), `response_types` (`code`), `token_endpoint_auth_method` (**só `none`**; outro valor, inclusive `private_key_jwt`, é `invalid_client_metadata`) e `scope` (o que não é identidade ou leitura é descartado; a resposta traz o `scope` efetivo).\n\nO client nasce `origem=dcr`, `publicado`, **não verificado** (a tela de consentimento mostra o selo \"Não verificado\" e o domínio dos redirects). Resposta `201` com `client_id` (`dcr_…`, CSPRNG) e `client_id_issued_at`. **Não há `client_secret`** (cliente público) e **não há `registration_access_token` nem `registration_client_uri`**: a gestão do registro (RFC 7592) não é suportada — para mudar, registre de novo; a equipe suspende pelo backoffice. Limite de 10 registros por hora por IP (`429` + `Retry-After`). Domínio de `redirect_uris` bloqueado => `400 invalid_redirect_uri`. Desligado (`OAUTH_DCR_ATIVO=false`) => `404`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/auth_RegistroClientePedido"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client registrado (sem segredo).",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_RegistroClienteResposta"
                }
              }
            }
          },
          "400": {
            "description": "`{\"error\":\"invalid_redirect_uri\"|\"invalid_client_metadata\",\"error_description\":\"…\"}` (RFC 7591 §3.2.2)."
          },
          "404": {
            "description": "Registro dinâmico desligado."
          },
          "429": {
            "description": "Limite de registros por hora do IP atingido (`Retry-After`)."
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.dcrH.Registrar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/userinfo": {
      "get": {
        "tags": [
          "OpenID Connect"
        ],
        "summary": "Claims do usuário do access token",
        "operationId": "userinfo",
        "x-asender-authz": "session",
        "description": "Devolve os claims do usuário dono do **access token** apresentado. Valida o Bearer por inteiro (RS256, `iss`, `exp`) e exige `tok_use: \"access\"` — um ID token NÃO é aceito aqui. Use para hidratar o perfil depois do login.",
        "security": [
          {
            "accessToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Os claims do usuário.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "sub",
                    "email",
                    "email_verified",
                    "name"
                  ],
                  "properties": {
                    "sub": {
                      "type": "string",
                      "description": "ID interno IMUTÁVEL do usuário (nunca o e-mail — piso 17); é o mesmo `sub` do id_token"
                    },
                    "email": {
                      "type": "string",
                      "format": "email",
                      "description": "e-mail primário atual"
                    },
                    "email_verified": {
                      "type": "boolean"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`invalid_token` — Bearer ausente, inválido, ou não é um access token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_ErroOAuth"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.UserInfo",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/logout": {
      "get": {
        "tags": [
          "OAuth 2.1"
        ],
        "summary": "Logout iniciado pelo app (end session)",
        "operationId": "logout",
        "x-asender-authz": "session",
        "description": "O `end_session_endpoint` (RP-initiated logout). Revoga a sessão **server-side** (piso 17: irreversível — não só o cookie) e a família de refresh dela, limpa o cookie e redireciona. Vai para o `post_logout_redirect_uri` SOMENTE se ele estiver na allowlist do client; caso contrário, para a tela de login. É uma navegação de browser.",
        "security": [],
        "parameters": [
          {
            "name": "post_logout_redirect_uri",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "destino após sair; precisa estar registrado no client, senão é ignorado"
          },
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "o client dono da allowlist de post-logout"
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "opaco, anexado ao `post_logout_redirect_uri` quando ele é aceito"
          }
        ],
        "responses": {
          "302": {
            "description": "Redireciona ao `post_logout_redirect_uri` (se permitido) ou à tela de login.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.Logout",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/oauth/revoke": {
      "post": {
        "tags": [
          "OAuth 2.1"
        ],
        "summary": "Revoga um refresh token (RFC 7009)",
        "operationId": "oauthRevoke",
        "x-asender-authz": "public",
        "description": "O client revoga um **refresh token seu**. Token desconhecido, de outro client ou já revogado responde `200` igual (RFC 7009 §2.2 — não vaza se existe). O access token é stateless e vive no máximo 15 min: a revogação efetiva do acesso é a do refresh (ou do grant, na tela \"Apps conectados\"). O client se autentica por HTTP Basic (`client_secret_basic`) ou pelos campos `client_id`/`client_secret` do corpo.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "o refresh token a revogar"
                  },
                  "token_type_hint": {
                    "type": "string",
                    "enum": [
                      "refresh_token",
                      "access_token"
                    ]
                  },
                  "client_id": {
                    "type": "string",
                    "description": "se não vier por HTTP Basic"
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "se não vier por HTTP Basic"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revogado (ou nada a revogar)."
          },
          "400": {
            "description": "`invalid_request` — corpo inválido ou sem `token`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_ErroOAuth"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_client` — client não autenticado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_ErroOAuth"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.Revoke",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/oauth/introspect": {
      "post": {
        "tags": [
          "OAuth 2.1"
        ],
        "summary": "Introspecção de token (RFC 7662)",
        "operationId": "oauthIntrospect",
        "x-asender-authz": "public",
        "description": "Diz se um token **do próprio client** está ativo. Só aceita client **confidencial** autenticado (Basic ou corpo): um client público não prova quem é. Token inválido, expirado, revogado, de outro client — ou de um grant que o usuário revogou — responde `{\"active\": false}` e nada mais. Access token de terceiro só é ativo enquanto o grant segue ativo.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "token_type_hint": {
                    "type": "string",
                    "enum": [
                      "refresh_token",
                      "access_token"
                    ]
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estado do token.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_Introspeccao"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` — corpo inválido ou sem `token`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_ErroOAuth"
                }
              }
            }
          },
          "401": {
            "description": "`invalid_client` — client não autenticado ou não confidencial.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth_ErroOAuth"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "s.oidcH.Introspect",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/acoes-sensiveis/{id}": {
      "get": {
        "servers": [
          {
            "url": "https://auth.asender.net",
            "description": "Identidade (OIDC / OAuth 2.1)"
          }
        ],
        "tags": [
          "auth"
        ],
        "summary": "Consultar atende GET /v1/acoes-sensiveis/{id}: o solicitante ou um admin do tenant.",
        "x-asender-servico": "asender-auth",
        "x-asender-handler": "h.Consultar",
        "x-asender-fonte": "codigo"
      }
    },
    "/dados/leads": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "página de leads em ordem `(atualizado_em, id)`, depois de `cursor` ou de `updated_after` (exclusivo). Contatos apagados/anonimizados não aparecem.",
        "description": "página de leads em ordem `(atualizado_em, id)`, depois de `cursor` ou de `updated_after` (exclusivo). Contatos apagados/anonimizados não aparecem.\n\nOnde é usada: BFF, `GET /v1/leads`.\n\nSaídas: 200 `{data, next_cursor}`; 400 parâmetro inválido; 401 sem credencial.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "i.ListarLeads",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "cria o lead da conta do principal (origem \"api\").",
        "description": "cria o lead da conta do principal (origem \"api\").\n\nOnde é usada: BFF, `POST /v1/leads`.\n\nSaídas: 201 lead público; 400 corpo inválido; 409 já existe (e-mail/telefone) ou chave em andamento; 413 corpo grande; 422 validação ou chave reusada; 401 sem credencial.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "i.CriarLead",
        "x-asender-fonte": "codigo"
      }
    },
    "/dados/leads/{id}": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "um lead pelo id público. 404 para id inexistente OU de outra conta — o mesmo 404, para não confirmar a existência a quem sonda ids.",
        "description": "um lead pelo id público. 404 para id inexistente OU de outra conta — o mesmo 404, para não confirmar a existência a quem sonda ids.\n\nOnde é usada: BFF, `GET /v1/leads/{id}`.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "i.ObterLead",
        "x-asender-fonte": "codigo"
      },
      "patch": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "altera só os campos presentes do lead.",
        "description": "altera só os campos presentes do lead.\n\nOnde é usada: BFF, `PATCH /v1/leads/{id}`.\n\nSaídas: 200 lead público; 404 inexistente OU de outra conta (o mesmo 404); demais como CriarLead.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "i.AtualizarLead",
        "x-asender-fonte": "codigo"
      }
    },
    "/dados/conversoes": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "página de conversões em ordem `(registrado_em, id)`, depois de `cursor` ou de `created_after` (exclusivo).",
        "description": "página de conversões em ordem `(registrado_em, id)`, depois de `cursor` ou de `created_after` (exclusivo).\n\nOnde é usada: BFF, `GET /v1/conversions`.\n\nSaídas: 200 `{data, next_cursor}`; 400 parâmetro inválido; 401 sem credencial.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "i.ListarConversoes",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/jornadas": {
      "get": {
        "tags": [
          "Jornadas"
        ],
        "summary": "Lista as jornadas da conta (ativas e inativas).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "as jornadas da conta.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "jornadas"
                  ],
                  "properties": {
                    "jornadas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/crm_Jornada"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/jornadas.\n\nOnde é usada: painel, tela de jornadas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "j.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Jornadas"
        ],
        "summary": "Cria uma jornada — sempre INATIVA.",
        "description": "Criar e ativar são operações separadas; a jornada nasce desligada. Passo `enviar_conversao`: a instalação é conferida no serviço de Integrações (422 se não existe nesta conta ou se o app não aceita conversão); com Integrações fora, salva e devolve `avisos` (piso 10).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoJornada"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "a jornada criada (com `avisos`, se algo não pôde ser conferido).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_JornadaComAvisos"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "j.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/jornadas/catalogo": {
      "get": {
        "tags": [
          "Jornadas"
        ],
        "summary": "Catálogo de gatilhos e ações do construtor visual.",
        "description": "Estático. Cada gatilho/ação com rótulo, grupo e campos tipados — inclui o gatilho `etapa_alterada` e a ação `enviar_conversao` (ADR-0120), os gatilhos `email_aberto`/`email_clicado` e as condições `email.*` (W3). A tela monta o formulário de cada nó a partir daqui.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "o catálogo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_CatalogoCanvas"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "j.CatalogoDoCanvas",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/jornadas/{id}": {
      "get": {
        "tags": [
          "Jornadas"
        ],
        "summary": "Lê uma jornada da conta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "200": {
            "description": "a jornada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Jornada"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/jornadas/{id}.\n\nOnde é usada: painel, tela de edição.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "j.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Jornadas"
        ],
        "summary": "Reescreve o desenho de uma jornada DESLIGADA.",
        "description": "Salva a edição (do construtor visual ou do diálogo): substitui nome/gatilho/passos/layout. Só a jornada desligada pode ser editada — editar uma ativa mexeria nos passos por baixo das inscrições em andamento (409). NÃO liga; ativar tem rota própria que revalida.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoJornada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "a jornada atualizada (com `avisos`, se algo não pôde ser conferido).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_JornadaComAvisos"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "409": {
            "description": "a jornada está ativa; desligue-a antes de editar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "j.Atualizar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/jornadas/{id}/ativa": {
      "put": {
        "tags": [
          "Jornadas"
        ],
        "summary": "Liga ou desliga a jornada.",
        "description": "Ligar revalida a jornada; desligar não. Ligada, ela passa a inscrever contatos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoAtivar"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "a jornada com o novo estado (com `avisos`, se algo não pôde ser conferido).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_JornadaComAvisos"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "j.Ativar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/campos": {
      "get": {
        "tags": [
          "Contatos"
        ],
        "summary": "Catálogo de campos nativos de contato e empresa.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "o catálogo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_CatalogoDeCampos"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/campos → `{versao, grupos, campos}` do contrato `asender_docs/contratos/campos-contato.json`.\n\nOnde é usada: tela de contatos (colunas e filtros), editor de contato e integrações.\n\nEfeitos: nenhum — o catálogo é estático, embutido no binário.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "handlers.Campos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/empresas": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "summary": "Lista as empresas da conta (também serve o seletor flutuante).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "trecho do nome ou do domínio",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "porte",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pais",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tipo_empresa",
            "in": "query",
            "required": false,
            "description": "opção do catálogo v2; outro valor → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "regime_tributario",
            "in": "query",
            "required": false,
            "description": "opção do catálogo v2; outro valor → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "segmento",
            "in": "query",
            "required": false,
            "description": "igualdade sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cnae",
            "in": "query",
            "required": false,
            "description": "prefixo",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "estado",
            "in": "query",
            "required": false,
            "description": "igualdade sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cidade",
            "in": "query",
            "required": false,
            "description": "igualdade sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ano_de",
            "in": "query",
            "required": false,
            "description": "ano de fundação mínimo",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "ano_ate",
            "in": "query",
            "required": false,
            "description": "ano de fundação máximo",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "qtd_min",
            "in": "query",
            "required": false,
            "description": "funcionários, mínimo",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "qtd_max",
            "in": "query",
            "required": false,
            "description": "funcionários, máximo",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          },
          {
            "$ref": "#/components/parameters/crm_Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "a página de empresas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaEmpresas"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "he.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Empresas"
        ],
        "summary": "Cria uma empresa.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoEmpresa"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "a empresa criada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Empresa"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "409": {
            "description": "CNPJ (ou nome sem CNPJ) já em uso na conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "he.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/empresas/{id}": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "summary": "Lê uma empresa da conta (outra conta = 404).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "200": {
            "description": "a empresa.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Empresa"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "he.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "tags": [
          "Empresas"
        ],
        "summary": "Altera uma empresa (parcial).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoEmpresa"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "a empresa alterada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Empresa"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "409": {
            "description": "CNPJ (ou nome sem CNPJ) já em uso na conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "PUT /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita. Mudar a configuração ACALMA o alerta: ele passa a observar outra coisa, e manter o estado faria a próxima avaliação comparar maçã com laranja — ele não avisaria pela condição NOVA, porque já estaria \"disparado\" pela antiga.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "he.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Empresas"
        ],
        "summary": "Remove a empresa (lógico) e solta os contatos dela — os contatos ficam.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "204": {
            "description": "removida."
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "DELETE /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "he.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/empresas/{id}/contatos": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "summary": "Contatos da empresa — mesma listagem e filtros de /v1/contatos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          },
          {
            "$ref": "#/components/parameters/crm_Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "a página de contatos da empresa.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaContatos"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/empresas/{id}/contatos — a MESMA listagem de /v1/contatos (filtros, cursor, forma), com `empresa_id` fixado pela rota.\n\nOnde é usada: ficha da empresa.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "he.Contatos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/empresas/{id}/pessoas": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/alertas.",
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Listar",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/empresas/{id}/pessoas {contato_id, papel}. Sempre entra como vínculo SECUNDÁRIO — tornar principal é a rota dedicada, que passa pela coluna `contatos.empresa_id` (a fonte da verdade, migration 031).",
        "description": "POST /v1/empresas/{id}/pessoas {contato_id, papel}. Sempre entra como vínculo SECUNDÁRIO — tornar principal é a rota dedicada, que passa pela coluna `contatos.empresa_id` (a fonte da verdade, migration 031).",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Vincular",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/empresas/{id}/pessoas/{contato_id}": {
      "delete": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "DELETE /v1/empresas/{id}/pessoas/{contato_id} → 204. Era o vínculo PRINCIPAL: `contatos.empresa_id` desta pessoa também é zerado (o repo cuida disso na mesma transação — nunca duas fontes divergentes).",
        "description": "DELETE /v1/empresas/{id}/pessoas/{contato_id} → 204. Era o vínculo PRINCIPAL: `contatos.empresa_id` desta pessoa também é zerado (o repo cuida disso na mesma transação — nunca duas fontes divergentes).",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Desvincular",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/empresas/{id}/pessoas/{contato_id}/principal": {
      "put": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PUT /v1/empresas/{id}/pessoas/{contato_id}/principal. A antiga principal (se houver, mesmo de outra empresa) vira secundária — nunca some.",
        "description": "PUT /v1/empresas/{id}/pessoas/{contato_id}/principal. A antiga principal (se houver, mesmo de outra empresa) vira secundária — nunca some.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.DefinirPrincipal",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/alertas": {
      "get": {
        "tags": [
          "Alertas"
        ],
        "summary": "Lista os alertas da conta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "os alertas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaAlertas"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ha.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Alertas"
        ],
        "summary": "Cria um alerta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoAlerta"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "o alerta criado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Alerta"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ha.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/alertas/eventos": {
      "get": {
        "tags": [
          "Alertas"
        ],
        "summary": "Lista o histórico de transições dos alertas da conta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          }
        ],
        "responses": {
          "200": {
            "description": "os eventos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaEventosAlerta"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/alertas/eventos?limite=N.\n\nOnde é usada: tela de alertas — a linha do tempo.\n\nEfeitos: uma leitura. Da CONTA, e não de um alerta: quem investiga \"o que aconteceu ontem\" quer a linha do tempo inteira, e paginar por alerta a obrigaria a abrir um por um.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ha.Eventos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/alertas/{id}": {
      "put": {
        "tags": [
          "Alertas"
        ],
        "summary": "Atualiza a configuração de um alerta (acalma-o).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoAlerta"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o alerta atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Alerta"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "PUT /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita. Mudar a configuração ACALMA o alerta: ele passa a observar outra coisa, e manter o estado faria a próxima avaliação comparar maçã com laranja — ele não avisaria pela condição NOVA, porque já estaria \"disparado\" pela antiga.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ha.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Alertas"
        ],
        "summary": "Remove um alerta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "204": {
            "description": "removido."
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "DELETE /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ha.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/destinos": {
      "get": {
        "tags": [
          "Destinos"
        ],
        "summary": "Lista os destinos (webhooks) da conta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "os destinos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaDestinos"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Destinos"
        ],
        "summary": "Cria um destino (webhook); cifra o segredo antes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoDestino"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "o destino criado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Destino"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/destinos/{id}": {
      "put": {
        "tags": [
          "Destinos"
        ],
        "summary": "Atualiza um destino; segredo ausente preserva o gravado.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoDestino"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o destino atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Destino"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "PUT /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita. Mudar a configuração ACALMA o alerta: ele passa a observar outra coisa, e manter o estado faria a próxima avaliação comparar maçã com laranja — ele não avisaria pela condição NOVA, porque já estaria \"disparado\" pela antiga.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Destinos"
        ],
        "summary": "Remove um destino.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "204": {
            "description": "removido."
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "DELETE /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/destinos/{id}/entregas": {
      "get": {
        "tags": [
          "Destinos"
        ],
        "summary": "Lista as tentativas de entrega de um destino (sem o payload).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          }
        ],
        "responses": {
          "200": {
            "description": "as entregas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaEntregas"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/destinos/{id}/entregas?limite=N.\n\nOnde é usada: diagnóstico da tela de integrações.\n\nEfeitos: uma leitura. O PAYLOAD não volta: ele carrega a PII do contato, e quem pergunta \"por que não chegou\" precisa do estado e do código — não do dado da pessoa repetido numa segunda tela.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Entregas",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/destinos/{id}/testar": {
      "post": {
        "tags": [
          "Destinos"
        ],
        "summary": "Enfileira uma entrega de teste pela mesma fila das reais.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "202": {
            "description": "teste enfileirado; acompanhe em /entregas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_TesteEnfileirado"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "description": "destino não encontrado ou inativo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "POST /v1/destinos/{id}/testar.\n\nOnde é usada: botão \"testar\" da tela de integrações.\n\nEfeitos: cria uma entrega pendente. # ENFILEIRA, e não entrega na hora A entrega passa pelo MESMO dreno das entregas de verdade — e portanto pelo mesmo guard do piso 18, pela mesma guarda de rede e pela mesma assinatura. Um caminho de teste que chamasse o webhook direto seria um segundo caminho de saída, e o segundo caminho é sempre o que esquece uma guarda. O operador vê o resultado em `GET /entregas`, que é a mesma tela onde ele vê as entregas reais — então \"o teste funcionou\" e \"a integração funciona\" são a mesma leitura, e não duas afirmações que podem divergir.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Testar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/integracoes-de-conversao": {
      "get": {
        "tags": [
          "Integrações de conversão"
        ],
        "summary": "Lista as integrações de conversão da conta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "as integrações.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaIntegracoes"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Integrações de conversão"
        ],
        "summary": "Cria uma integração de conversão (provedor meta; sem URL própria).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoDestino"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "a integração criada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Destino"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/integracoes-de-conversao/{id}": {
      "put": {
        "tags": [
          "Integrações de conversão"
        ],
        "summary": "Atualiza uma integração de conversão.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoDestino"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "a integração atualizada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Destino"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "PUT /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita. Mudar a configuração ACALMA o alerta: ele passa a observar outra coisa, e manter o estado faria a próxima avaliação comparar maçã com laranja — ele não avisaria pela condição NOVA, porque já estaria \"disparado\" pela antiga.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Integrações de conversão"
        ],
        "summary": "Remove uma integração de conversão.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "204": {
            "description": "removida."
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "DELETE /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/integracoes-de-conversao/{id}/entregas": {
      "get": {
        "tags": [
          "Integrações de conversão"
        ],
        "summary": "Lista as tentativas de entrega de uma integração (sem o payload).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          }
        ],
        "responses": {
          "200": {
            "description": "as entregas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaEntregas"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/destinos/{id}/entregas?limite=N.\n\nOnde é usada: diagnóstico da tela de integrações.\n\nEfeitos: uma leitura. O PAYLOAD não volta: ele carrega a PII do contato, e quem pergunta \"por que não chegou\" precisa do estado e do código — não do dado da pessoa repetido numa segunda tela.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Entregas",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/integracoes-de-conversao/{id}/testar": {
      "post": {
        "tags": [
          "Integrações de conversão"
        ],
        "summary": "Enfileira uma entrega de teste da integração pela mesma fila das reais.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "202": {
            "description": "teste enfileirado; acompanhe em /entregas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_TesteEnfileirado"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "description": "integração não encontrada ou inativa.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "POST /v1/destinos/{id}/testar.\n\nOnde é usada: botão \"testar\" da tela de integrações.\n\nEfeitos: cria uma entrega pendente. # ENFILEIRA, e não entrega na hora A entrega passa pelo MESMO dreno das entregas de verdade — e portanto pelo mesmo guard do piso 18, pela mesma guarda de rede e pela mesma assinatura. Um caminho de teste que chamasse o webhook direto seria um segundo caminho de saída, e o segundo caminho é sempre o que esquece uma guarda. O operador vê o resultado em `GET /entregas`, que é a mesma tela onde ele vê as entregas reais — então \"o teste funcionou\" e \"a integração funciona\" são a mesma leitura, e não duas afirmações que podem divergir.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hd.Testar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/retencao": {
      "get": {
        "tags": [
          "Configurações"
        ],
        "summary": "Lê a política de retenção da conta.",
        "description": "Conta sem política responde 200 com `dias: null`, não 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "a política.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_RespostaRetencao"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hc.ObterRetencao",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Configurações"
        ],
        "summary": "Define a política de retenção.",
        "description": "`dias` ausente é 400; `null` guarda indefinidamente; fora de 30..3650 é 422.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoRetencao"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "a política salva.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_RespostaRetencao"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hc.DefinirRetencao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/uso": {
      "get": {
        "tags": [
          "Configurações"
        ],
        "summary": "Lê o consumo da conta (números locais + quota do core).",
        "description": "Os números locais sempre voltam; com o core fora, `core_disponivel: false`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "name": "periodo",
            "in": "query",
            "required": false,
            "description": "mês no formato AAAA-MM (padrão, o mês corrente).",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "o consumo da conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_RespostaUso"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hc.Uso",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/contatos": {
      "get": {
        "tags": [
          "Contatos"
        ],
        "summary": "Lista uma página de contatos da conta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "name": "estagio",
            "in": "query",
            "required": false,
            "description": "filtra por estágio (allowlist); valor desconhecido → 400.",
            "schema": {
              "type": "string",
              "enum": [
                "novo",
                "contatado",
                "qualificado",
                "cliente",
                "descartado"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "trecho de nome+sobrenome; com @ busca o e-mail EXATO (índice cego); com 8+ dígitos busca o telefone EXATO (forma canônica)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pais",
            "in": "query",
            "required": false,
            "description": "ISO-2; outro formato → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "estado",
            "in": "query",
            "required": false,
            "description": "igualdade sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cidade",
            "in": "query",
            "required": false,
            "description": "igualdade sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "empresa_id",
            "in": "query",
            "required": false,
            "description": "id público da empresa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cargo",
            "in": "query",
            "required": false,
            "description": "trecho, sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "marcador",
            "in": "query",
            "required": false,
            "description": "um marcador",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dono_id",
            "in": "query",
            "required": false,
            "description": "responsável",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fonte",
            "in": "query",
            "required": false,
            "description": "fonte do lead, sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "utm_source",
            "in": "query",
            "required": false,
            "description": "UTM source da primeira captura",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dispositivo",
            "in": "query",
            "required": false,
            "description": "desktop|mobile|tablet|bot|desconhecido (último envio); outro valor → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "so",
            "in": "query",
            "required": false,
            "description": "sistema do último envio, sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "navegador",
            "in": "query",
            "required": false,
            "description": "navegador do último envio, sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ip_pais",
            "in": "query",
            "required": false,
            "description": "ISO-2 do país pelo IP do último envio; outro formato → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email_status",
            "in": "query",
            "required": false,
            "description": "ok|suspeito|dominio_inexistente|descartavel|desconhecido; outro valor → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "genero",
            "in": "query",
            "required": false,
            "description": "opção do catálogo v2; outro valor → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nivel_hierarquico",
            "in": "query",
            "required": false,
            "description": "opção do catálogo v2; outro valor → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "origem_canal",
            "in": "query",
            "required": false,
            "description": "canal_origem do catálogo v2; outro valor → 400",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "landing_page",
            "in": "query",
            "required": false,
            "description": "landing page da primeira captura, igualdade sem caixa",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "criado_de",
            "in": "query",
            "required": false,
            "description": "AAAA-MM-DD ou RFC 3339 (inclusivo)",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "criado_ate",
            "in": "query",
            "required": false,
            "description": "AAAA-MM-DD (inclui o dia) ou RFC 3339",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          },
          {
            "$ref": "#/components/parameters/crm_Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "a página de contatos e o cursor seguinte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaContatos"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "c.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Contatos"
        ],
        "summary": "Cria um contato; cifra a PII antes de gravar.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoContato"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "o contato criado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Contato"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "409": {
            "description": "já existe contato com aquele identificador.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "c.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/contatos/{id}": {
      "get": {
        "tags": [
          "Contatos"
        ],
        "summary": "Lê um contato da conta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "200": {
            "description": "o contato.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Contato"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "c.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "tags": [
          "Contatos"
        ],
        "summary": "Atualiza um contato (parcial — campo ausente preserva).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoContato"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o contato atualizado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Contato"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "PUT /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita. Mudar a configuração ACALMA o alerta: ele passa a observar outra coisa, e manter o estado faria a próxima avaliação comparar maçã com laranja — ele não avisaria pela condição NOVA, porque já estaria \"disparado\" pela antiga.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "c.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Contatos"
        ],
        "summary": "Apaga um contato (soft delete, reversível pelo painel master).",
        "description": "Carimba `apagado_em/por/motivo` e registra `contato_apagado` na timeline. Nada é destruído:\na PII fica, o contato some das leituras do cliente e o painel master restaura. A anonimização\nirreversível NÃO acontece aqui — só por pedido de exclusão aprovado por um admin da conta\n(`/v1/pedidos-exclusao`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoApagamento"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "apagado (soft delete)."
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "503": {
            "description": "apagamento indisponível neste serviço.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "c.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/contatos/{id}/promover": {
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/contatos/{id}/promover (D1, 028/029). Sem corpo — a ação é binária, e um corpo que aceitasse `promovido_em`/`promovido_por` do cliente deixaria a data e o autor da promoção sob controle de quem chama a API.",
        "description": "POST /v1/contatos/{id}/promover (D1, 028/029). Sem corpo — a ação é binária, e um corpo que aceitasse `promovido_em`/`promovido_por` do cliente deixaria a data e o autor da promoção sob controle de quem chama a API.\n\nOnde é usada: botão \"Promover a contato\" nas telas Leads e Contatos.\n\nEfeitos: escreve a coluna, a timeline (`ciclo_mudou`) e o outbox (`crm.contact.promoted`), atômico (repo.ContatoPG.MudarCiclo). Promover quem já é contato responde 200 sem gerar nova atividade nem novo evento (idempotente) — não é erro repetir o clique.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "c.Promover",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/contatos/{id}/rebaixar": {
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/contatos/{id}/rebaixar — reverte uma promoção feita por engano.",
        "description": "POST /v1/contatos/{id}/rebaixar — reverte uma promoção feita por engano.\n\nOnde é usada: ação \"Rebaixar a lead\" na ficha do contato.\n\nEfeitos: escreve a coluna e a timeline (`ciclo_mudou`); NÃO publica evento — só a promoção é fato que jornadas/relatórios querem (doc-comment de `eventos.SubjectContatoPromovido`).",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "c.Rebaixar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/contatos/exportar.csv": {
      "get": {
        "tags": [
          "Contatos"
        ],
        "summary": "Exporta os contatos da conta em CSV (registra a exportação na timeline).",
        "description": "Devolve CSV (não JSON), com Content-Disposition attachment. Registra a operação na timeline ANTES de o arquivo sair (LGPD art. 37); sem trilha disponível, responde 503. Teto de 10.000 linhas.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          }
        ],
        "responses": {
          "200": {
            "description": "o CSV dos contatos.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "503": {
            "description": "exportação indisponível (trilha desligada neste serviço).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "c.Exportar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/contatos/{id}/atividades": {
      "get": {
        "tags": [
          "Timeline"
        ],
        "summary": "Lista a timeline de um contato (paginada).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          },
          {
            "$ref": "#/components/parameters/crm_Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "a página de atividades, o cursor seguinte e o total.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaAtividades"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "t.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Timeline"
        ],
        "summary": "Anexa uma nota à timeline do contato.",
        "description": "Só NOTA — os outros tipos são consequência do que o sistema fez. O autor vem da sessão.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoNota"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "a nota criada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Atividade"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "t.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/contatos/{id}/consentimento": {
      "get": {
        "tags": [
          "Consentimento"
        ],
        "summary": "Lê a preferência do contato, canal a canal.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "200": {
            "description": "o que o contato recebe por canal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Consentimento"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "cs.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Consentimento"
        ],
        "summary": "Muda a preferência de um canal (um por vez).",
        "description": "`recebe` é obrigatório; sua ausência é 422 para não descadastrar por engano.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoConsentimento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o estado relido do banco.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Consentimento"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "cs.Definir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/contatos/{id}/visita-pages": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "vp.Obter",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/contatos/{id}/conversas": {
      "get": {
        "tags": [
          "Timeline"
        ],
        "summary": "Lista as conversas do atendimento ligadas a este contato.",
        "description": "Projeção LOCAL (`crm.contato_conversas`, migration 056), alimentada pelo consumo de `atendimento.conversa.criada`/`.identificada` — nunca uma chamada síncrona ao asender_atendimento_api (piso 10). Por isso o item é RASO: só `conversa_id` (opaco, do atendimento), `canal` e `vinculado_em` — sem estado nem prévia de mensagem.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          },
          {
            "$ref": "#/components/parameters/crm_Limite"
          }
        ],
        "responses": {
          "200": {
            "description": "as conversas, mais recente primeiro.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaConversasDoContato"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "cc.Listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/contatos/{id}/identidades": {
      "get": {
        "tags": [
          "Identidades"
        ],
        "summary": "Lista as identidades ativas do contato (valor SEMPRE mascarado).",
        "description": "E-mail, telefone, WhatsApp, Telegram e LID do contato. O valor em claro nunca sai por aqui: só `valor_mascarado`. Identidades desvinculadas não aparecem. Contato de outra conta responde 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "200": {
            "description": "as identidades, da mais antiga para a mais nova.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaIdentidadesDoContato"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ic.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Identidades"
        ],
        "summary": "Liga uma identidade a um contato que já existe (fonte `manual`).",
        "description": "Serve sobretudo para ligar um LID do WhatsApp (`lid:<id>`) ou um id do Telegram (`tg:<dígitos>`) a um contato existente: sem isso essas conversas nunca viram lead. Depois de ligada, a próxima conversa nessa identidade anexa ao contato. Valida o formato por `tipo` (telefone E.164 com `+`, e-mail, `tg:<dígitos>`, `lid:<id>`). A unicidade é por conta (índice cego): se o endereço já é de OUTRO contato, responde 409 com o id público dele e a tentativa é enfileirada em `/v1/identidades/sugestoes` (não existe fluxo de mesclagem automática, ADR-0061). Ligar de novo ao mesmo contato é idempotente (200). Auditado: o autor vem da sessão. Mesmo nível de acesso do PATCH do contato.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoIdentidadeManual"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "já estava ligada a este contato.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_IdentidadeDoContato"
                }
              }
            }
          },
          "201": {
            "description": "identidade ligada agora.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_IdentidadeDoContato"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "409": {
            "description": "o endereço já pertence a outro contato desta conta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ConflitoDeIdentidade"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ic.Vincular",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/contatos/{id}/identidades/{identidade_id}/desvincular": {
      "post": {
        "tags": [
          "Identidades"
        ],
        "summary": "Desvincula (soft) uma identidade do contato.",
        "description": "NÃO há DELETE (ADR-0112): a linha fica com `desvinculada_em/por/motivo` e a identidade deixa de resolver (conversas novas nela não anexam mais a este contato). Auditado na timeline (sem o valor). O e-mail/telefone PRINCIPAL da ficha não se desvincula aqui (422): edite o contato.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          },
          {
            "name": "identidade_id",
            "in": "path",
            "required": true,
            "description": "id público da identidade (`idn_…`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoDesvinculo"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "desvinculada."
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ic.Desvincular",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/clientes": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/alertas.",
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hcl.Listar",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/alertas.",
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hcl.Criar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/clientes/{id}": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hcl.Obter",
        "x-asender-fonte": "codigo"
      },
      "patch": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PUT /v1/alertas/{id}.",
        "description": "PUT /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita. Mudar a configuração ACALMA o alerta: ele passa a observar outra coisa, e manter o estado faria a próxima avaliação comparar maçã com laranja — ele não avisaria pela condição NOVA, porque já estaria \"disparado\" pela antiga.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hcl.Atualizar",
        "x-asender-fonte": "codigo"
      },
      "delete": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "DELETE /v1/alertas/{id}.",
        "description": "DELETE /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hcl.Apagar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/projetos/colunas": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/projetos/colunas.",
        "description": "GET /v1/projetos/colunas.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.ListarColunas",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/projetos/colunas.",
        "description": "POST /v1/projetos/colunas.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.CriarColuna",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/projetos/colunas/{id}": {
      "patch": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PATCH /v1/projetos/colunas/{id}.",
        "description": "PATCH /v1/projetos/colunas/{id}.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.AtualizarColuna",
        "x-asender-fonte": "codigo"
      },
      "delete": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "DELETE /v1/projetos/colunas/{id}.",
        "description": "DELETE /v1/projetos/colunas/{id}.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.ApagarColuna",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/projetos/colunas/{id}/mover": {
      "put": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PUT /v1/projetos/colunas/{id}/mover.",
        "description": "PUT /v1/projetos/colunas/{id}/mover.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.MoverColuna",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/projetos": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/alertas.",
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Listar",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/alertas.",
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Criar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/projetos/{id}": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Obter",
        "x-asender-fonte": "codigo"
      },
      "patch": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PUT /v1/alertas/{id}.",
        "description": "PUT /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita. Mudar a configuração ACALMA o alerta: ele passa a observar outra coisa, e manter o estado faria a próxima avaliação comparar maçã com laranja — ele não avisaria pela condição NOVA, porque já estaria \"disparado\" pela antiga.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Atualizar",
        "x-asender-fonte": "codigo"
      },
      "delete": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "DELETE /v1/alertas/{id}.",
        "description": "DELETE /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Apagar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/projetos/{id}/mover": {
      "put": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PUT /v1/projetos/{id}/mover.",
        "description": "PUT /v1/projetos/{id}/mover.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.Mover",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/projetos/{id}/logo": {
      "put": {
        "tags": [
          "Projetos"
        ],
        "summary": "Troca a logo do projeto (multipart, campo `arquivo`).",
        "description": "Mesma capacidade de editar o projeto. Ordem das recusas: projeto de outra conta = 404; > 512 KiB = 413 `ArquivoGrande` (corte no leitor); antivírus nos bytes ORIGINAIS — infectado = 422 `ArquivoInfectado`, scanner configurado e fora = 503 `AntivirusIndisponivel` (fail-closed, com `Retry-After`); tipo (magic bytes) fora de PNG/JPEG, imagem corrompida ou dimensão fora de 32..2048 px = 422 `FormatoInvalido`. A imagem gravada é a RE-CODIFICADA. WebP não é aceito (sem dependência extra).",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoLogo"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o projeto atualizado (`tem_logo` verdadeiro e novo `logo_versao`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Projeto"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "413": {
            "description": "arquivo acima de 512 KiB (`ArquivoGrande`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ErroLogo"
                }
              }
            }
          },
          "422": {
            "description": "`FormatoInvalido` ou `ArquivoInfectado`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ErroLogo"
                }
              }
            }
          },
          "503": {
            "description": "antivírus indisponível (`AntivirusIndisponivel`); tente de novo (`Retry-After`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ErroLogo"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.EnviarLogo",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Projetos"
        ],
        "summary": "Remove a logo do projeto (idempotente).",
        "description": "204 haja logo ou não; 404 só se o projeto não é da conta. Mesma capacidade de editar o projeto.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "204": {
            "description": "removida (ou já não havia)."
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.RemoverLogo",
        "x-asender-fonte": "spec+codigo"
      },
      "get": {
        "tags": [
          "Projetos"
        ],
        "summary": "Os bytes da logo (já re-codificada).",
        "description": "Mesma capacidade de ler o projeto. `Content-Type` fixado pelo tipo re-codificado, `X-Content-Type-Options: nosniff`, `Content-Security-Policy: default-src 'none'; sandbox`, `Cache-Control: private, max-age=3600` e `ETag` = sha256 (`If-None-Match` igual = 304). 404 neutro para projeto inexistente, de outra conta, na lixeira ou sem logo.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "a imagem.",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Content-Type-Options": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "nosniff"
                  ]
                }
              },
              "Content-Security-Policy": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "o `If-None-Match` ainda vale; sem corpo."
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hp.ServirLogo",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/tarefas": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/alertas.",
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ht.Listar",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/alertas.",
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ht.Criar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/tarefas/{id}": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ht.Obter",
        "x-asender-fonte": "codigo"
      },
      "patch": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PUT /v1/alertas/{id}.",
        "description": "PUT /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita. Mudar a configuração ACALMA o alerta: ele passa a observar outra coisa, e manter o estado faria a próxima avaliação comparar maçã com laranja — ele não avisaria pela condição NOVA, porque já estaria \"disparado\" pela antiga.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ht.Atualizar",
        "x-asender-fonte": "codigo"
      },
      "delete": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "DELETE /v1/alertas/{id}.",
        "description": "DELETE /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ht.Apagar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/tarefas/{id}/mover": {
      "put": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PUT /v1/projetos/{id}/mover.",
        "description": "PUT /v1/projetos/{id}/mover.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "ht.Mover",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/painel": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hpn.Obter",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/relatorios/funil": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "Funil responde GET /v1/relatorios/funil?de=&ate=.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hr.Funil",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/relatorios/origem": {
      "get": {
        "tags": [
          "Painel"
        ],
        "summary": "Leads criados no período, por fonte, por formulário e por canal de conversa.",
        "description": "`por_canal_conversa` conta os leads nascidos de uma conversa do atendimento (`origem_modo` começando em `conversa:`), agrupados pelo canal: `whatsweb`, `telegram`, `whatsapp`, `email`, `chat`. Conversas importadas do histórico (`origem=historico`) nunca criam lead e portanto não entram na contagem.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "name": "de",
            "in": "query",
            "required": false,
            "description": "início do período (AAAA-MM-DD)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ate",
            "in": "query",
            "required": false,
            "description": "fim do período (AAAA-MM-DD)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "as três agregações do período.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "periodo": {
                      "type": "object"
                    },
                    "por_fonte": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/crm_ContagemPorChave"
                      }
                    },
                    "por_formulario": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/crm_ContagemPorChave"
                      }
                    },
                    "por_canal_conversa": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/crm_ContagemPorChave"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hr.Origem",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/relatorios/atividade": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "Atividade responde GET /v1/relatorios/atividade?de=&ate=.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hr.Atividade",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/relatorios/projetos": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "Projetos responde GET /v1/relatorios/projetos?de=&ate=.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hr.Projetos",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/relatorios/tarefas": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "Tarefas responde GET /v1/relatorios/tarefas?de=&ate=.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hr.Tarefas",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/relatorios/trafego": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "duas leituras INDEPENDENTES — uma consulta local (`LeadsPorOrigemUTM`) e uma chamada HTTP ao Pages (`pages.Resumo`, reaproveitando `top_origens`, já calculado com o mesmo critério que a Início usa) — casadas em memória pela string de `utm_source`. NUNCA um JOIN de banco: os dois dados vivem em bancos de serviços diferentes (piso 6/10), e cruzá-los aqui, na borda, é o único jeito de fazer isso sem um serviço ler o schema do outro.",
        "description": "duas leituras INDEPENDENTES — uma consulta local (`LeadsPorOrigemUTM`) e uma chamada HTTP ao Pages (`pages.Resumo`, reaproveitando `top_origens`, já calculado com o mesmo critério que a Início usa) — casadas em memória pela string de `utm_source`. NUNCA um JOIN de banco: os dois dados vivem em bancos de serviços diferentes (piso 6/10), e cruzá-los aqui, na borda, é o único jeito de fazer isso sem um serviço ler o schema do outro.\n\nOnde é usada: tela de Relatórios do CRM.\n\nEfeitos: uma consulta local; uma chamada HTTP ao Pages. Pages fora do ar NÃO derruba a rota: ela responde 200 com `disponivel: false` e as linhas só com o lado do CRM (sessões ficam de fora, e `taxa_de_conversao` sai vazia em todas) — o operador ainda vê \"quantos leads por origem\", só sem cruzar com sessão.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hr.Trafego",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/importacoes/campos": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/campos → `{versao, grupos, campos}` do contrato `asender_docs/contratos/campos-contato.json`.",
        "description": "GET /v1/campos → `{versao, grupos, campos}` do contrato `asender_docs/contratos/campos-contato.json`.\n\nOnde é usada: tela de contatos (colunas e filtros), editor de contato e integrações.\n\nEfeitos: nenhum — o catálogo é estático, embutido no binário.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Campos",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/importacoes/previa": {
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/importacoes/previa (multipart, campo \"arquivo\").",
        "description": "POST /v1/importacoes/previa (multipart, campo \"arquivo\").\n\nOnde é usada: primeiro passo da tela de importação, assim que a pessoa solta o CSV.\n\nEfeitos: nenhum — nada é persistido aqui (ver o doc-comment de `Servico.Prevalidar` para o porquê de o mesmo arquivo ser reenviado no passo de confirmação).",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Prevalidar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/importacoes": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/alertas.",
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Listar",
        "x-asender-fonte": "codigo"
      },
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/importacoes (multipart: campo \"arquivo\" + campo \"config\" com o JSON de `pedidoIniciar`) — o MESMO arquivo da prévia, reenviado.",
        "description": "POST /v1/importacoes (multipart: campo \"arquivo\" + campo \"config\" com o JSON de `pedidoIniciar`) — o MESMO arquivo da prévia, reenviado.\n\nOnde é usada: botão \"confirmar importação\" da tela de mapeamento.\n\nEfeitos: uma escrita (o lote, status `pendente`); o processamento em si acontece depois, em segundo plano — a resposta não espera por ele.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Iniciar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/importacoes/{id}": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Obter",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/importacoes/{id}/erros.csv": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/importacoes/{id}/erros.csv.",
        "description": "GET /v1/importacoes/{id}/erros.csv.\n\nEfeitos: uma leitura. # Mesma neutralização de fórmula da exportação de contatos O MOTIVO de um erro pode ecoar um valor da própria linha (\"e-mail inválido: =cmd|'/c calc'!A1\"): se um CSV hostil chegou como upload, o relatório de erro NÃO pode virar o vetor de saída da mesma injeção — por isso reaproveita `exportacao.Escritor`, o mesmo pacote que já neutraliza a exportação de contatos.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.ErrosCSV",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/importacoes/{id}/desfazer": {
      "post": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "POST /v1/importacoes/{id}/desfazer.",
        "description": "POST /v1/importacoes/{id}/desfazer.\n\nOnde é usada: botão \"desfazer importação\" do relatório final.\n\nEfeitos: anonimiza os contatos que o lote criou (ver o doc-comment de `Servico.Desfazer` para o porquê de não tocar os que ele só atualizou).",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Desfazer",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/identidades/sugestoes": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/alertas.",
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Listar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/identidades/sugestoes/{id}/decisao": {
      "put": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "PUT /v1/identidades/sugestoes/{id}/decisao",
        "description": "PUT /v1/identidades/sugestoes/{id}/decisao\n\nOnde é usada: tela do atendente, nos botões \"é a mesma pessoa\" / \"não é\".\n\nEfeitos: uma escrita. # ACEITAR NÃO FUNDE NADA Esta rota grava que um humano concordou que os dois contatos são a mesma pessoa. As duas fichas continuam existindo, separadas, com e-mail, telefone, timeline, consentimento e dono intactos. Fundir automaticamente é decisão do dono e está PARQUEADA (ADR-0061) — e é por isso que a resposta desta rota devolve a sugestão decidida, e não um contato.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Decidir",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/identidades/resolver": {
      "get": {
        "tags": [
          "Identidades"
        ],
        "summary": "Traduz uma identidade (e-mail, telefone…) no contato que a tem.",
        "description": "Não funde nada (ADR-0061): só diz se ALGUM contato já tem este endereço neste canal, e qual. O `valor` nunca é ecoado nem logado em claro além do necessário para calcular o índice cego.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "name": "canal",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email",
                "telefone",
                "whatsapp",
                "chat_visitante",
                "telegram"
              ]
            }
          },
          {
            "name": "valor",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "o e-mail ou telefone em claro, nunca persistido fora do índice cego."
          }
        ],
        "responses": {
          "200": {
            "description": "achou (e o contato), ou achou:false.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_IdentidadeResolvida"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          },
          "503": {
            "description": "resolução de identidade indisponível (cifrador não configurado).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hi.Resolver",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/segmentos": {
      "get": {
        "tags": [
          "Segmentos"
        ],
        "summary": "Lista os segmentos vivos da conta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "os segmentos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_ListaSegmentos"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "sg.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Segmentos"
        ],
        "summary": "Cria um segmento; devolve o alcance atual.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoSegmento"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "o segmento criado e seu alcance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_SegmentoComAlcance"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "409": {
            "description": "já existe um segmento com esse nome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "POST /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "sg.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/segmentos/previa": {
      "post": {
        "tags": [
          "Segmentos"
        ],
        "summary": "Calcula o alcance de um segmento AINDA NÃO salvo.",
        "description": "Não grava nada; responde quantas pessoas os critérios alcançam agora.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoSegmento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o alcance calculado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Alcance"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "sg.Previsualizar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/segmentos/{id}": {
      "get": {
        "tags": [
          "Segmentos"
        ],
        "summary": "Lê um segmento e quantas pessoas ele alcança agora.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "200": {
            "description": "o segmento e seu alcance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_SegmentoComAlcance"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "sg.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Segmentos"
        ],
        "summary": "Substitui nome, conjunção e critérios de um segmento.",
        "description": "PUT (não PATCH) — um segmento é sua lista de critérios; manda-se o conjunto inteiro.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoSegmento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o segmento atualizado e seu alcance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_SegmentoComAlcance"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "409": {
            "description": "já existe um segmento com esse nome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "sg.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Segmentos"
        ],
        "summary": "Remove (logicamente) um segmento.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "responses": {
          "204": {
            "description": "removido."
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "description": "DELETE /v1/alertas/{id}.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "sg.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pedidos-exclusao": {
      "get": {
        "tags": [
          "Pedidos de exclusão"
        ],
        "summary": "Lista a fila de pedidos de exclusão (só admin da conta).",
        "description": "A decisão de autorização é do servidor (PDP, produto `admin`, nível >= 30). Quem não é admin recebe 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pendente",
                "aprovado",
                "recusado",
                "executado"
              ]
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "a página de pedidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_PedidosExclusaoPagina"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hpe.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Pedidos de exclusão"
        ],
        "summary": "Registra o pedido de exclusão de um titular (task pendente; NÃO apaga nada).",
        "description": "Qualquer membro da conta registra. Cria a task, a auditoria e avisa os admins pela central de notificações.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoExclusaoNovo"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "o pedido pendente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_PedidoExclusao"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/crm_CorpoInvalido"
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "409": {
            "description": "já existe um pedido pendente para este contato.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hpe.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pedidos-exclusao/{id}/aprovar": {
      "post": {
        "tags": [
          "Pedidos de exclusão"
        ],
        "summary": "Aprova o pedido e anonimiza o contato (só admin da conta; irreversível).",
        "description": "Único caminho HTTP até a anonimização. Grava timeline e auditoria na mesma transação.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoExclusaoDecisao"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o pedido executado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_PedidoExclusao"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "409": {
            "description": "o pedido já foi decidido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hpe.Aprovar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pedidos-exclusao/{id}/recusar": {
      "post": {
        "tags": [
          "Pedidos de exclusão"
        ],
        "summary": "Recusa o pedido (só admin da conta; motivo obrigatório; nada é apagado).",
        "description": "O contato permanece intacto e a recusa fica na timeline e na auditoria.",
        "parameters": [
          {
            "$ref": "#/components/parameters/crm_TenantHeader"
          },
          {
            "$ref": "#/components/parameters/crm_Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/crm_PedidoExclusaoDecisao"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o pedido recusado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_PedidoExclusao"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/crm_SemSessao"
          },
          "404": {
            "$ref": "#/components/responses/crm_NaoEncontrado"
          },
          "409": {
            "description": "o pedido já foi decidido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/crm_Erro"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/crm_RegraInvalida"
          }
        },
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "hpe.Recusar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/lixeira": {
      "get": {
        "tags": [
          "Pixels"
        ],
        "summary": "Lista o que a conta apagou e ainda pode restaurar (lixeira do usuário)",
        "description": "ADR-0121. Duas implementações do MESMO contrato: o asender_pages_api serve `link`, `dominio`, `gratis` (subdomínio grátis), `midia`, `kit`, `visao`, `roteamento` e `rascunho`; o asender_pages_runtime serve os tipos de pixel abaixo. Só sessão (chave de API é 401). No pages_api o nome é legível (título, host) e o `apagado_por_nome` vem do auth. Pixel: origens autorizadas (`pixeldominio`, id `pxd_…`, nome = host) e conversões (`pixelconversao`, id `pxc_…`, nome = nome da conversão) removidas DA CONTA da sessão (tenant nunca vem do cliente), do mais recente ao mais antigo, até 50 por tipo. Sem `tipo`, traz os dois (cursor vazio). `tipo` fora da allowlist → 404. Não há purga. `apagado_por_nome` vem quando o auth resolve o nome; auth fora = campo omitido.",
        "parameters": [
          {
            "name": "tipo",
            "in": "query",
            "description": "Filtra por tipo (ver `ItemLixeira.tipo`); tipo fora da allowlist do serviço é 404.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "O `cursor` devolvido pela página anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Os itens apagados e o cursor da próxima página ('' = fim).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "itens",
                    "cursor"
                  ],
                  "properties": {
                    "itens": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_ItemLixeira"
                      }
                    },
                    "cursor": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "404": {
            "description": "tipo fora da allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "422": {
            "description": "cursor inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.LixeiraUsuario.Listar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/lixeira/{tipo}/{id}/restaurar": {
      "post": {
        "tags": [
          "Pixels"
        ],
        "summary": "Restaura um item da lixeira da conta",
        "description": "ADR-0121. No pages_api: `link` volta no estado de antes (ativo ou pausado) e dá 409 `conflito_unicidade` se o endereço curto foi reocupado, ou `dependencia_indisponivel` se o domínio do link não é mais da conta; `dominio` volta PENDENTE de verificação e sem TLS (a verificação normal reconfere o DNS e só então autoriza o certificado), com 409 `conflito_unicidade` se outro domínio ativo usa o host e `cota_excedida` se o plano não comporta; `gratis` (subdomínio grátis apagado pelo usuário) volta ao mesmo nome, ainda verificado (a posse é da plataforma, sem DNS de terceiro nem efeito externo), sob a MESMA trava de cota do cadastro: 409 `cota_excedida` se não cabe e 409 `conflito_unicidade` se o nome foi tomado por outra conta ou está na quarentena de 30 dias de OUTRA conta (a própria conta pode voltar ao nome dentro da quarentena). Troca de nome e derrubada pela plataforma (takedown) não entram na lixeira e dão 404; `kit` exige o papel de editor (403) e revalida os vínculos: se ainda aponta para domínio ou página apagados, 409 `dependencia_indisponivel` (nada é desvinculado em silêncio; restaure o domínio/página ou refaça o vínculo). Os demais voltam como estavam. No runtime: devolve a origem/conversão à conta da sessão — mesma permissão de apagá-la (só sessão; a sessão de suporte é somente leitura). 404 quando o item não está apagado, é de outra conta, o pixel pai foi apagado ou o tipo está fora da allowlist; 409 `conflito_unicidade` quando o host/nome já pertence a outro item ativo do pixel. A restauração é logada com origem `usuario`.",
        "parameters": [
          {
            "name": "tipo",
            "in": "path",
            "required": true,
            "description": "Um tipo de `ItemLixeira.tipo`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "O `id` devolvido pela listagem.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "restaurado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "403": {
            "description": "restaurar kit sem o papel de editor no Pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "404": {
            "description": "não está na lixeira",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "409": {
            "description": "`conflito_unicidade` (endereço, host ou nome já em uso por outro item ativo), `dependencia_indisponivel` (domínio do link, ou domínio/página vinculados ao kit) ou `cota_excedida` (plano).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "motivo": {
                      "type": "string",
                      "enum": [
                        "conflito_unicidade",
                        "dependencia_indisponivel",
                        "cota_excedida"
                      ]
                    },
                    "erro": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.LixeiraUsuario.Restaurar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/dados/jornadas": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/alertas.",
        "description": "GET /v1/alertas.\n\nOnde é usada: tela de alertas.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "h.Listar",
        "x-asender-fonte": "codigo"
      }
    },
    "/dados/jornadas/{id}": {
      "get": {
        "servers": [
          {
            "url": "https://api.crm.asender.net",
            "description": "CRM"
          }
        ],
        "tags": [
          "crm_api"
        ],
        "summary": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "description": "GET /v1/clientes/{id}. Outra conta = 404, como inexistente.",
        "x-asender-servico": "asender_crm_api",
        "x-asender-handler": "h.Obter",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/dominios/{id}/cloudflare": {
      "get": {
        "tags": [
          "Domínios"
        ],
        "summary": "Estado do DNS automático na Cloudflare",
        "description": "Diz se a zona está na Cloudflare (NS / `_domainconnect`), se o botão \"Conectar com Cloudflare\" (Domain Connect) está disponível e se há token guardado. Ritmo: 60/min por conta. Domínio de outra conta = 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "estado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_EstadoCloudflare"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "429": {
            "description": "muitas tentativas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Estado",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dominios/{id}/cloudflare/domain-connect": {
      "post": {
        "tags": [
          "Domínios"
        ],
        "summary": "URL assinada do Domain Connect (1 clique)",
        "description": "Monta a URL da tela de consentimento da Cloudflare para o template do Asender, só com os grupos que faltam, ASSINADA (RSA-SHA256). `retorno` é o caminho da tela de domínios (`/t/<conta>/dominios`); o host de volta vem do servidor. Na volta (`?cloudflare=<id>`) a tela só chama a verificação. Ritmo: 10/min por conta.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "retorno"
                ],
                "properties": {
                  "retorno": {
                    "type": "string",
                    "example": "/t/acme/dominios"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "grupos": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "Domain Connect indisponível para o domínio (ver `motivo`) ou nada a aplicar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "retorno inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "description": "muitas tentativas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.DomainConnect",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dominios/{id}/cloudflare/token": {
      "put": {
        "tags": [
          "Domínios"
        ],
        "summary": "Guardar o token da Cloudflare (Zone → DNS → Edit)",
        "description": "Valida o token na API da Cloudflare (a zona do domínio tem de estar visível e o DNS legível), cifra (AES-256-GCM) e guarda. O token nunca é devolvido. Ritmo: 10/min.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "estado novo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_EstadoCloudflare"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "422": {
            "description": "formato inválido",
            "token recusado ou sem acesso à zona": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "description": "muitas tentativas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "Cloudflare indisponível ou cifra não configurada neste ambiente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.SalvarToken",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Domínios"
        ],
        "summary": "Remover o token guardado",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "removido (também quando não havia)"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "429": {
            "description": "muitas tentativas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/dominios/{id}/cloudflare/token → 204 (também sem token).\n\nOnde é usada: botão \"remover token\".\n\nEfeitos: uma escrita e uma linha de auditoria. O token continua válido NA Cloudflare: a tela pede para o cliente revogá-lo lá.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.RemoverToken",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dominios/{id}/cloudflare/aplicar": {
      "post": {
        "tags": [
          "Domínios"
        ],
        "summary": "Aplicar na Cloudflare os registros do Asender",
        "description": "Com o token guardado, cria ou atualiza SÓ os registros do Asender — idempotente, nunca apaga registro alheio, `proxied:false` em CNAME/A/AAAA. Cada aplicação vai para a trilha de auditoria (também quando falha no meio). Ritmo: 10/min.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "relatório por registro",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_RelatorioCloudflare"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "sem token guardado (ou token ilegível — cole de novo)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "token recusado",
            "sem permissão de edição ou sem acesso à zona": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "description": "muitas tentativas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "Cloudflare indisponível",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Aplicar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/links": {
      "get": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Listar links do encurtador",
        "parameters": [
          {
            "name": "busca",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Nome, destino, slug ou código (sem caixa, sem curinga)."
          },
          {
            "name": "pagina_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Só os links criados pelo editor desta página."
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "links": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Link"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "uma página da trilha (`id desc`) com os filtros da query.\n\nSaídas: 200 `{itens, proximo_cursor}` (\"\" = fim); 422 filtro inválido (cursor, limite, de/ate fora de RFC3339); 500 falha de leitura.\n\nEfeitos: só leitura.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Criar link (código curto + UTM + parâmetros + repasse)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_LinkEntrada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "o editor já tinha criado este link (origem pagina, mesmo bloco e destino)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Link"
                }
              }
            }
          },
          "201": {
            "description": "criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Link"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "409": {
            "description": "endereço /r/ já em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "destino/configuração/domínio inválidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/dominios — valida (422 para inválido/reservado, sufixo público ou sob a base dos grátis), classifica pela PSL, confere a cota da classe (402 `{recurso, limite, usado}`; 503 sem plataforma) e grava.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. # O domínio nasce NÃO verificado E é isso que impede o pior desfecho do produto: sem a prova de DNS, qualquer conta declararia `banco.com.br` e passaria a receber certificado TLS para esse nome — servindo uma página de captura no domínio de outra pessoa, com cadeado verde.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/links/previa": {
      "post": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Prévia da URL final (mesma regra do redirect)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/pages_LinkEntrada"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "entrada": {
                        "type": "string",
                        "description": "Query de exemplo que chegaria no link curto (?gclid=…)."
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url_final": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "422": {
            "description": "destino/configuração inválidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/links/previa — valida destino e configuração e devolve `{url_final}` com a MESMA função que o runtime usa a cada clique.\n\nOnde é usada: editor de link do painel (prévia ao vivo, enquanto a pessoa digita).\n\nEfeitos: nenhum — não grava nada. A prévia vem do servidor, e não de uma terceira cópia da regra em TypeScript: o que a pessoa vê é exatamente o que o clique vai fazer.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Previa",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/links/{id}": {
      "get": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Obter link",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Link"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/paginas/{id}/experimento.\n\nOnde é usada: tela de A/B do painel.\n\nEfeitos: uma leitura. Página SEM experimento responde 200 com o corpo vazio de variantes, e não 404: \"esta página não tem teste\" é uma resposta sobre o mundo, e a tela precisa dela para mostrar o botão de criar. 404 mandaria a tela tratar o caso normal como erro.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Editar link (código, origem e contagem não mudam)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_LinkEntrada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Link"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "endereço /r/ já em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "destino/configuração/domínio inválidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PUT /v1/formularios/{id}.\n\nOnde é usada: edição.\n\nEfeitos: escreve a linha.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Apagar link (o código nunca é reaproveitado)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "apagado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/dominios/{id}.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. Apagar TIRA a autorização de TLS, e é o único caminho para isso — de propósito: um domínio verificado que pudesse ser \"desverificado\" por outro caminho sairia do ar sem ninguém entender por quê.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/links/{id}/relatorio": {
      "get": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Relatório de cliques do link",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "de",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Padrão: 29 dias antes de `ate`."
          },
          {
            "name": "ate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Padrão: hoje (America/Sao_Paulo). Período máximo 366 dias."
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_RelatorioLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "422": {
            "description": "período inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/links/{id}/relatorio?de=&ate= — totais, únicos, robôs, série por dia (fuso America/Sao_Paulo) e quebras por origem, dispositivo, país e UTM; 404 para link de outra conta.\n\nOnde é usada: tela de relatório do link.\n\nEfeitos: leituras.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Relatorio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/marca": {
      "get": {
        "tags": [
          "Marca"
        ],
        "summary": "Obter a marca da conta",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "marca": {
                      "$ref": "#/components/schemas/pages_Marca"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/paginas/{id}/experimento.\n\nOnde é usada: tela de A/B do painel.\n\nEfeitos: uma leitura. Página SEM experimento responde 200 com o corpo vazio de variantes, e não 404: \"esta página não tem teste\" é uma resposta sobre o mundo, e a tela precisa dela para mostrar o botão de criar. 404 mandaria a tela tratar o caso normal como erro.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Marca"
        ],
        "summary": "Definir a marca da conta",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Marca"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "gravada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "marca": {
                      "$ref": "#/components/schemas/pages_Marca"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem o papel de editor no Pages (PEP nível 20)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "valor inválido — a mensagem nomeia o campo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PUT /v1/configuracoes/marca; campo vazio APAGA aquele elemento; o resto do kit padrão fica como está. Conta sem kit ganha o \"Padrão\".\n\nOnde é usada: clientes do contrato anterior à 037. O corpo NÃO tem `tenant_id`: a conta vem da sessão, e o campo desconhecido é recusado (400) — o silêncio faria quem tentou achar que funcionou.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Definir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/marca/kits": {
      "get": {
        "tags": [
          "Marca"
        ],
        "summary": "Listar os kits de marca da conta",
        "description": "O padrão primeiro; cada kit traz quantos domínios/páginas o usam. Leitura no nível 0 (quem acessa o Pages).",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kits": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_KitMarcaResumo"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.ListarKits",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Marca"
        ],
        "summary": "Criar (ou duplicar) um kit de marca",
        "description": "O primeiro kit da conta nasce padrão. `duplicar_de` copia os dados de outro kit da conta. Exige papel de editor.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_KitMarcaPedido"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kit": {
                      "$ref": "#/components/schemas/pages_KitMarca"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido (inclui campo desconhecido, ex. tenant_id)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem o papel de editor no Pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "description": "kit de origem (duplicar_de) não existe nesta conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "409": {
            "description": "já existe um kit com esse nome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "campo inválido (a mensagem nomeia o campo) ou limite de kits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.CriarKit",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/marca/kits/{id}": {
      "get": {
        "tags": [
          "Marca"
        ],
        "summary": "Obter um kit de marca",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kit": {
                      "$ref": "#/components/schemas/pages_KitMarca"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "description": "não existe — ou é de outra conta (mesma resposta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/configuracoes/marca/kits/{id}; kit de outra conta = 404.\n\nOnde é usada: formulário de edição do kit.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.ObterKit",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Marca"
        ],
        "summary": "Salvar um kit de marca (nome e dados de uma vez)",
        "description": "PUT: o que não vier é apagado. A resposta traz os valores NORMALIZADOS (URL canônica de rede, E.164, CEP, documento só com dígitos/letras). Se o kit é o padrão, `pages.marcas` é reprojetada na mesma transação.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_KitMarcaPedido"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "gravado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kit": {
                      "$ref": "#/components/schemas/pages_KitMarca"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido (campo desconhecido; duplicar_de só na criação)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem o papel de editor no Pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "description": "não existe — ou é de outra conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "409": {
            "description": "já existe um kit com esse nome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "campo inválido — a mensagem nomeia o campo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.SalvarKit",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Marca"
        ],
        "summary": "Apagar um kit de marca",
        "description": "Recusado (409) para o kit padrão e para kit em uso por domínio/página — reatribua antes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "apagado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem o papel de editor no Pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "description": "não existe — ou é de outra conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "409": {
            "description": "kit padrão ou em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.ApagarKit",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/marca/kits/{id}/padrao": {
      "post": {
        "tags": [
          "Marca"
        ],
        "summary": "Tornar um kit o padrão da conta",
        "description": "O padrão vale para todo domínio/página sem kit próprio.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kit": {
                      "$ref": "#/components/schemas/pages_KitMarca"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem o papel de editor no Pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "description": "não existe — ou é de outra conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.DefinirPadrao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/marca/vinculos": {
      "get": {
        "tags": [
          "Marca"
        ],
        "summary": "Onde cada kit vale (domínios e páginas)",
        "description": "Todo domínio vivo da conta com o kit escolhido (`kit_id` vazio = padrão) e as páginas com kit próprio.",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_KitMarcaVinculos"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Vinculos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/marca/vinculos/dominios/{id}": {
      "put": {
        "tags": [
          "Marca"
        ],
        "summary": "Escolher o kit de um domínio",
        "description": "`kit_id` vazio volta ao padrão da conta.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id público do domínio",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_KitMarcaVinculoPedido"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "gravado"
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem o papel de editor no Pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "description": "domínio ou kit não existe nesta conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.VincularDominio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/marca/vinculos/paginas/{id}": {
      "put": {
        "tags": [
          "Marca"
        ],
        "summary": "Escolher o kit de uma página",
        "description": "`kit_id` vazio volta ao kit do domínio (ou ao padrão).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id público da página",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_KitMarcaVinculoPedido"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "gravado"
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem o papel de editor no Pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "description": "página ou kit não existe nesta conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.VincularPagina",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/marca/efetivo": {
      "get": {
        "tags": [
          "Marca"
        ],
        "summary": "Kit de marca efetivo de uma página/host",
        "description": "Fallback página → domínio → padrão. Contrato único de consumo do construtor (autopreenchimento) e da geração por IA. Conta sem kit: `origem: vazio`, `kit: null`.",
        "parameters": [
          {
            "name": "pagina",
            "in": "query",
            "required": false,
            "description": "id público da página",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dominio",
            "in": "query",
            "required": false,
            "description": "host; ausente = o da página",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_KitMarcaEfetivo"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Efetivo",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/email/midias": {
      "get": {
        "tags": [
          "Mídia"
        ],
        "summary": "Biblioteca de imagens do e-mail marketing (listar)",
        "security": [
          {
            "sessao": []
          }
        ],
        "description": "A MESMA galeria da conta (`GET /v1/midias`), para o builder de e-mail. Só sessão (chave de API é 401); exige acesso ao produto `emailmarketing` (e não `pages`) — sem ele, 404. A `url` devolvida é a pública https do host de mídia, a que o cliente de e-mail do destinatário baixa.",
        "parameters": [
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "teto de itens"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "midias": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "description": "sem acesso ao produto emailmarketing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "mds.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Mídia"
        ],
        "summary": "Biblioteca de imagens do e-mail marketing (enviar)",
        "security": [
          {
            "sessao": []
          }
        ],
        "description": "Igual a `POST /v1/midias` (multipart, campo `arquivo`; tipo por conteúdo, teto 10 MB, antivírus), com o PEP do produto `emailmarketing` no nível operador. Sem `de-url` e sem apagar por esta rota.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "arquivo"
                ],
                "properties": {
                  "arquivo": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "já existia (deduplicada por conteúdo)"
          },
          "201": {
            "description": "enviada"
          },
          "400": {
            "description": "campo `arquivo` ausente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "description": "sem acesso (nível operador) ao produto emailmarketing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "413": {
            "description": "arquivo grande demais",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "tipo não aceito ou recusado pelo antivírus",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "antivírus sem responder (reenvie; vem `Retry-After`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "mds.Upload",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dominios/{id}/push": {
      "get": {
        "tags": [
          "Domínios"
        ],
        "summary": "Coleta de Web Push nativa do domínio (ADR-0102)",
        "description": "Estado da coleta de inscrições de push neste domínio. Sem configuração responde `{\"ligado\":false}`. Domínio de outra conta = 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "estado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_PushDoDominio"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Domínios"
        ],
        "summary": "Ligar ou configurar a coleta de Web Push do domínio",
        "description": "Grava só valores PÚBLICOS emitidos pela ferramenta Push (`GET /v1/push/site-config` da asender_push_api): chave VAPID pública, chave de site `pk_push_…` e a base da API, mais o convite. Com `ligado:true`, as páginas do domínio servem `/asender-sw.js` e o convite de inscrição. Exige papel de editor. O domínio precisa estar verificado (409).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_PushDoDominioEntrada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "configuração gravada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_PushDoDominio"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/pages_PedidoInvalidoAutoria"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem papel de editor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "domínio ainda não verificado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "valor inválido (chave",
            "base": null,
            "convite)": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Salvar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Domínios"
        ],
        "summary": "Desligar a coleta de Web Push do domínio",
        "description": "Idempotente. Mantém os valores gravados para religar sem nova emissão.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "desligado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "sem papel de editor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Desligar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/seo": {
      "get": {
        "tags": [
          "SEO"
        ],
        "summary": "Obter o SEO e o compartilhamento de uma página",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:read"
            ]
          }
        ],
        "description": "Página nunca configurada responde 200 com os campos vazios — o runtime usa os padrões (título ← nome da página, descrição ← primeiro texto, og:image ← primeira imagem ou a do site).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "seo": {
                      "$ref": "#/components/schemas/pages_SEOPagina"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "description": "não existe — ou é de outra conta (mesma resposta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.ObterPagina",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "SEO"
        ],
        "summary": "Definir o SEO e o compartilhamento de uma página",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "description": "Todos os campos de uma vez; vazio volta ao padrão. Vale na próxima resolução do runtime, sem publicar versão.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_SEOPagina"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "gravado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "seo": {
                      "$ref": "#/components/schemas/pages_SEOPagina"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido (inclui campo desconhecido, ex. tenant_id)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "description": "não existe — ou é de outra conta (mesma resposta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "campo inválido — a mensagem nomeia o campo (tamanho, URL fora de http/https, imagem sem https, dimensões)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.SalvarPagina",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/configuracoes/seo": {
      "get": {
        "tags": [
          "SEO"
        ],
        "summary": "Obter o SEO do site (um host da conta)",
        "description": "Desde a 041 só `noindex` é do site. Os demais campos de SEOSite são o ESPELHO, só leitura, do kit de marca efetivo do host, e `seo.kit` identifica o kit ({id, nome, origem}).",
        "parameters": [
          {
            "name": "dominio",
            "in": "query",
            "required": false,
            "description": "host do domínio verificado da conta; ausente/vazio = o host da plataforma",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "seo": {
                      "$ref": "#/components/schemas/pages_SEOSite"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "description": "host não é um domínio verificado desta conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.ObterSite",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "SEO"
        ],
        "summary": "Definir a indexação do site (um host da conta)",
        "description": "Grava só `noindex`, que muda robots.txt, sitemap.xml e o robots do <head> de todas as páginas do host. Nome do site, imagem, favicon e organização moram no kit de marca (041): mandá-los preenchidos aqui dá 422.",
        "parameters": [
          {
            "name": "dominio",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_SEOSite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "gravado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "seo": {
                      "$ref": "#/components/schemas/pages_SEOSite"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido (inclui campo desconhecido, ex. tenant_id)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "description": "host não é um domínio verificado desta conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "campo que mora no kit de marca veio preenchido — a mensagem diz onde editá-lo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.SalvarSite",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/relatorios/visoes": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Listar as visões salvas da conta",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "visoes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_VisaoDeRelatorio"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "uma página da trilha (`id desc`) com os filtros da query.\n\nSaídas: 200 `{itens, proximo_cursor}` (\"\" = fim); 422 filtro inválido (cursor, limite, de/ate fora de RFC3339); 500 falha de leitura.\n\nEfeitos: só leitura.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Analytics"
        ],
        "summary": "Salvar o recorte atual como visão",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_PedidoDeVisao"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "visao": {
                      "$ref": "#/components/schemas/pages_VisaoDeRelatorio"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido (inclui campo desconhecido, ex. tenant_id)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "409": {
            "description": "já existe visão com esse nome (sem diferenciar maiúscula)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "recorte inválido, ou a conta já tem 50 visões",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/dominios — valida (422 para inválido/reservado, sufixo público ou sob a base dos grátis), classifica pela PSL, confere a cota da classe (402 `{recurso, limite, usado}`; 503 sem plataforma) e grava.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. # O domínio nasce NÃO verificado E é isso que impede o pior desfecho do produto: sem a prova de DNS, qualquer conta declararia `banco.com.br` e passaria a receber certificado TLS para esse nome — servindo uma página de captura no domínio de outra pessoa, com cadeado verde.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/relatorios/visoes/{id}": {
      "put": {
        "tags": [
          "Analytics"
        ],
        "summary": "Atualizar uma visão",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_PedidoDeVisao"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "atualizada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "visao": {
                      "$ref": "#/components/schemas/pages_VisaoDeRelatorio"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "description": "não existe — ou é de outra conta (mesma resposta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "409": {
            "description": "nome repetido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "recorte inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PUT /v1/formularios/{id}.\n\nOnde é usada: edição.\n\nEfeitos: escreve a linha.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Analytics"
        ],
        "summary": "Apagar uma visão",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "apagada"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "description": "não existe — ou é de outra conta (mesma resposta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/dominios/{id}.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. Apagar TIRA a autorização de TLS, e é o único caminho para isso — de propósito: um domínio verificado que pudesse ser \"desverificado\" por outro caminho sairia do ar sem ninguém entender por quê.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas": {
      "get": {
        "tags": [
          "Páginas"
        ],
        "summary": "Listar as páginas da conta",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "paginas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Pagina"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "uma página da trilha (`id desc`) com os filtros da query.\n\nSaídas: 200 `{itens, proximo_cursor}` (\"\" = fim); 422 filtro inválido (cursor, limite, de/ate fora de RFC3339); 500 falha de leitura.\n\nEfeitos: só leitura.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Páginas"
        ],
        "summary": "Criar página",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "description": "Cria uma página. Um `dominio` próprio exige posse PROVADA do host (422 se não verificado); um `template` com conteúdo privilegiado (bloco ou item `html`, HTML cru) exige nível pelo PEP (403). O `template` pode ser v1 ou v2 (ver `TemplatePagina`); recusa de v2 responde 422 com `codigo` e `caminho` (`ErroConteudo`).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_CriarPagina"
              },
              "examples": {
                "minima": {
                  "value": {
                    "titulo": "Lançamento",
                    "slug": "promo"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Pagina"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "conteúdo privilegiado (html) sem nível",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "409": {
            "description": "já existe página nesse endereço",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "título ausente / domínio não verificado / template fora do contrato (v2: com `codigo` e `caminho`)",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/pages_ErroConteudo"
                    },
                    {
                      "$ref": "#/components/schemas/pages_ErroAutoria"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/arquivadas": {
      "get": {
        "tags": [
          "Páginas"
        ],
        "summary": "Listar as páginas arquivadas da conta (lixeira)",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:read"
            ]
          }
        ],
        "description": "Lixeira do usuário (ADR-0112): as páginas que a conta arquivou, da mais recente para a mais antiga (até 200). Só lê; para trazer uma de volta use `POST /v1/paginas/{id}/desarquivar`.",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "paginas"
                  ],
                  "properties": {
                    "paginas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_PaginaArquivada"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.ListarArquivadas",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/importar": {
      "post": {
        "tags": [
          "Páginas"
        ],
        "summary": "Importar um arquivo exportado, copiando a mídia para a conta",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "description": "Cria uma página (despublicada) a partir do `template` de um arquivo exportado (`asender_pages_template: 2`) e COPIA para a biblioteca da conta da sessão toda mídia referenciada (imagem de item, galeria, fundo de seção/coluna/página, ícone enviado, avatar/logo, imagem de compartilhamento): baixa de novo no servidor (mesmas guardas de SSRF da importação por URL, até 3 redirecionamentos, teto por arquivo e por importação, sniff de tipo, SVG sanitizado, antivírus) e troca cada URL pela nova. O que não puder ser copiado MANTÉM a URL original e aparece no relatório como `mantido_original` com o motivo. Referências presas à conta de origem (`form_id` que a conta não tem, `*_rastreio`, vínculos) são removidas, com aviso. A conta vem da sessão; o corpo não aceita `tenant_id`. Regras de `POST /v1/paginas` (domínio verificado, item privilegiado) valem aqui.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_ImportarPagina"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criada; o relatório diz o que foi copiado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ResultadoImportacao"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido (campo desconhecido",
            "inclusive tenant_id)": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "conteúdo privilegiado (html) sem nível",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "409": {
            "description": "já existe página nesse endereço (checado antes de copiar)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "título/template ausente, endereço inválido, SEO inválido ou template fora do contrato",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/pages_ErroConteudo"
                    },
                    {
                      "$ref": "#/components/schemas/pages_ErroAutoria"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "500": {
            "description": "não consegui preparar a importação (por exemplo, conferir os formulários)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.ImportarPagina",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/midias/copiar": {
      "post": {
        "tags": [
          "Páginas"
        ],
        "summary": "Tentar copiar de novo as mídias que ficaram na URL original",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "description": "Roda a cópia de mídia sobre a ÚLTIMA versão da página (a mídia que já é da conta vira `ja_na_conta`, sem duplicar). Se alguma URL mudou, grava uma versão nova (não publica); senão responde 200 sem versão nova.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "nada mudou",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ResultadoRecopia"
                }
              }
            }
          },
          "201": {
            "description": "versão nova gravada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ResultadoRecopia"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "conteúdo privilegiado (html) sem nível",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "description": "a cópia de mídia está indisponível neste servidor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.RecopiarMidias",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}": {
      "get": {
        "tags": [
          "Páginas"
        ],
        "summary": "Obter uma página (com suas versões)",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Pagina"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/paginas/{id}/experimento.\n\nOnde é usada: tela de A/B do painel.\n\nEfeitos: uma leitura. Página SEM experimento responde 200 com o corpo vazio de variantes, e não 404: \"esta página não tem teste\" é uma resposta sobre o mundo, e a tela precisa dela para mostrar o botão de criar. 404 mandaria a tela tratar o caso normal como erro.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "tags": [
          "Páginas"
        ],
        "summary": "Editar slug, domínio ou título",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_EditarPagina"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "atualizada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Pagina"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido / nenhum campo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "endereço em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "domínio não verificado / título vazio",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PATCH /v1/paginas/{id} — renomear, mudar o endereço público ou o domínio.\n\nOnde é usada: tela de páginas do painel (renomear) e configurações da página.\n\nEfeitos: uma escrita. # Só o que veio é tocado `nil` é \"não mandado\" e `&\"\"` é \"mandado vazio\". Sem essa distinção, editar o título apagaria o endereço público da página — e ela sairia do ar sem ninguém ter pedido. # Validação IGUAL à da criação O slug e o título passam pelas mesmas regras do `Criar`. Um PATCH mais frouxo seria a porta dos fundos: bastaria criar válido e editar para inválido para pôr no ar um endereço que a criação recusa.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Editar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Páginas"
        ],
        "summary": "Arquivar a página (soft delete — despublica e esconde; dá para desarquivar)",
        "description": "SÓ sessão (chave de API é recusada: chave nunca apaga). Despublica a página e a esconde da lista; as **versões e o rascunho ficam guardados** e dá para desfazer em `POST /v1/paginas/{id}/desarquivar` (volta despublicada; o painel master também restaura). Nada é destruído (ADR-0112). Página inexistente, de outra conta ou já arquivada responde 404 (indistinguível).",
        "security": [
          {
            "sessao": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "204": {
            "description": "arquivada"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "description": "arquivar indisponível neste serviço"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/versoes": {
      "post": {
        "tags": [
          "Páginas"
        ],
        "summary": "Salvar uma nova versão (auto-save do editor)",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "template"
                ],
                "properties": {
                  "template": {
                    "$ref": "#/components/schemas/pages_TemplatePagina"
                  }
                }
              },
              "examples": {
                "v2": {
                  "value": {
                    "template": {
                      "versao": 2,
                      "secoes": [
                        {
                          "id": "s1",
                          "preset": "hero",
                          "colunas": [
                            {
                              "id": "c1",
                              "alinhamento_horizontal": "centro",
                              "itens": [
                                {
                                  "id": "i1",
                                  "tipo": "titulo",
                                  "props": {
                                    "texto": "Lançamento",
                                    "nivel": 1
                                  }
                                },
                                {
                                  "id": "i2",
                                  "tipo": "botao",
                                  "props": {
                                    "rotulo": "Quero",
                                    "url": "#form"
                                  }
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "salva",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "versao": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "description": "conteúdo privilegiado (html) sem nível",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "422": {
            "description": "template fora do contrato; v2 responde `codigo` + `caminho` do nó",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/pages_ErroConteudo"
                    },
                    {
                      "$ref": "#/components/schemas/pages_ErroAutoria"
                    }
                  ]
                },
                "examples": {
                  "cor_injetada": {
                    "value": {
                      "erro": "documento: prop de item inválida: secoes[1].colunas[0].itens[2].props.cor \"red;}\"",
                      "codigo": "ErrPropInvalida",
                      "caminho": "secoes[1].colunas[0].itens[2].props.cor"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/paginas/{id}/versoes.\n\nOnde é usada: botão salvar do editor.\n\nEfeitos: cria uma versão.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Salvar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/rascunho": {
      "get": {
        "tags": [
          "Páginas"
        ],
        "summary": "Ler o rascunho pendente da página",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:read"
            ]
          }
        ],
        "description": "O rascunho é UM por página, guardado SEM criar versão e SEM publicar. `rascunho_em` é a etiqueta de concorrência otimista; `rascunho_por` aparece quando foi uma chave de API quem gravou (`apikey:<key_id>`). Sem rascunho: 204.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_RascunhoPagina"
                }
              }
            }
          },
          "204": {
            "description": "sem rascunho pendente"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.ObterRascunho",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Páginas"
        ],
        "summary": "Gravar o rascunho (sem criar versão e sem publicar)",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "description": "Guarda o `template` como ele está, SEM as regras de publicação (só a forma: objeto JSON dentro do teto de 512 KiB) — elas rodam ao salvar versão e ao publicar. `?base=<rascunho_em>` é a concorrência otimista: se o rascunho do servidor mudou desde o que você leu, nada é gravado e a resposta é **409** (`codigo: RascunhoConflito`) trazendo o rascunho do servidor — os dois lados ficam preservados. `?base=` vazio significa \"esperava não haver rascunho\". Na sessão `?base=` é opcional (clientes antigos gravam sem condição); na **API key é OBRIGATÓRIO** (428 sem ele): o agente nunca pisa no trabalho de uma pessoa.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          },
          {
            "name": "base",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "o `rascunho_em` que você leu; vazio = esperava nenhum rascunho"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "template"
                ],
                "properties": {
                  "template": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "gravado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rascunho_em": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido ou `base` ilegível",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "o rascunho mudou desde a `base` (`codigo: RascunhoConflito`); o corpo traz `rascunho` do servidor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ConflitoDeRascunho"
                }
              }
            }
          },
          "413": {
            "description": "rascunho acima do teto",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "428": {
            "description": "chave de API sem `?base=`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.SalvarRascunho",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Páginas"
        ],
        "summary": "Descartar o rascunho pendente",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "description": "Idempotente; versões e a publicada não mudam. Página de outra conta responde 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "204": {
            "description": "descartado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.DescartarRascunho",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/publicar": {
      "post": {
        "tags": [
          "Páginas"
        ],
        "summary": "Publicar uma versão",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:publish"
            ]
          }
        ],
        "description": "Publica a versão indicada; na sessão, sem corpo publica a última salva. **API key** (`pages:publish`): o corpo com `versao` é OBRIGATÓRIO (422 sem ele) — a chave publica uma versão que ela mesma identificou, nunca \"a mais recente\" às cegas (que poderia ser uma edição de pessoa ainda não revisada). Publicar é visível ao público e indexável.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "versao": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "publicada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "versao_publicada": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "422": {
            "description": "essa versão não existe",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Publicar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/desarquivar": {
      "post": {
        "tags": [
          "Páginas"
        ],
        "summary": "Desarquivar uma página (volta despublicada)",
        "description": "SÓ sessão (chave de API é recusada, como em arquivar). Devolve a página arquivada à lista como **rascunho** (arquivar despublica; republicar é decisão sua). Versões e rascunho estão intactos. Registra evento de auditoria. Página inexistente, de outra conta ou não arquivada responde 404 (indistinguível). Se outra página viva já usa o mesmo endereço, responde 409 com `codigo: slug_em_uso` e nada é sobrescrito.",
        "security": [
          {
            "sessao": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "204": {
            "description": "desarquivada"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "o endereço (domínio + caminho) já é de outra página",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "codigo",
                    "erro"
                  ],
                  "properties": {
                    "codigo": {
                      "type": "string",
                      "enum": [
                        "slug_em_uso"
                      ]
                    },
                    "erro": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "lixeira indisponível neste serviço"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Desarquivar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/experimento": {
      "get": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Obter o experimento da página",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/paginas/{id}/experimento.\n\nOnde é usada: tela de A/B do painel.\n\nEfeitos: uma leitura. Página SEM experimento responde 200 com o corpo vazio de variantes, e não 404: \"esta página não tem teste\" é uma resposta sobre o mundo, e a tela precisa dela para mostrar o botão de criar. 404 mandaria a tela tratar o caso normal como erro.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.Obter",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/experimento/auto-stop": {
      "put": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Definir o critério de decisão e o autobalanceamento (ADR-0093)",
        "description": "Campos ausentes mantêm o valor gravado. Aceito com o teste rodando — nada aqui reatribui visitante já contado. Campo desconhecido (inclusive o antigo `margem_para_encerrar`, removido em 2026-10-07) é recusado com 400.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "minimo_por_variante": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "visitas por variante antes de qualquer conclusão; 0 = 100"
                  },
                  "prob_vencedora": {
                    "type": "number",
                    "description": "0 (= 0,95) ou 0,5–0,999: chance de ser a melhor para 'vencedor provável'"
                  },
                  "autobalancear": {
                    "type": "boolean",
                    "description": "alocação adaptativa (Thompson em lote) para visitantes novos"
                  },
                  "piso_autobalancear": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "description": "fatia mínima (pontos) de cada variante ativa"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "definido"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "422": {
            "description": "valores fora de faixa",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.DefinirAutoStop",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/variantes": {
      "get": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Listar as variantes",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "variantes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Variante"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/paginas/{id}/variantes.\n\nOnde é usada: tela de A/B.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.ListarVariantes",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Acrescentar uma PÁGINA ao teste — duplicar ou vincular (ADR-0095)",
        "description": "`{id}` é a página da URL do teste; sem teste aberto, ele nasce aqui com `{id}` como principal. \"duplicar\" cria uma página normal nova (cópia da última versão de `pagina_id`, ou da principal), publicada, com slug `<url>-ab-<braço>` e sem rota própria. \"vincular\" liga uma página existente do MESMO domínio que não esteja em outro teste aberto. A divisão volta a ser igual. 409 com o teste rodando (pause antes).",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "modo"
                ],
                "additionalProperties": false,
                "properties": {
                  "modo": {
                    "type": "string",
                    "enum": [
                      "duplicar",
                      "vincular"
                    ]
                  },
                  "pagina_id": {
                    "type": "string",
                    "description": "vincular: a página escolhida; duplicar: a origem (vazio = principal)"
                  },
                  "id": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{1,64}$",
                    "description": "id do braço; vazio = próximo livre (a, b, …)"
                  },
                  "nome": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "o experimento atualizado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "teste rodando / id de braço já usado / página já em outro teste",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "modo inválido / página de outro domínio / id fora do formato",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.CriarVariante",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/variantes/{varianteID}": {
      "put": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Renomear o braço e/ou ligar a rota própria da página-variante",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          },
          {
            "name": "varianteID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "nome": {
                    "type": "string"
                  },
                  "rota_propria": {
                    "type": "boolean",
                    "description": "a variante responde também no próprio slug, indexável"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PUT /v1/paginas/{id}/variantes/{varianteID}. Conteúdo, título e SEO da página se editam no editor normal — aqui só o que é do VÍNCULO.\n\nOnde é usada: tela de A/B.\n\nEfeitos: uma escrita; aceito com o teste rodando.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.EditarVariante",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Desvincular a página do braço (ela continua existindo)",
        "description": "Os pesos que ficam renormalizam; a amostra do braço fica no histórico. Saindo a principal, o braço de maior peso assume a URL; sobrando uma página, o teste encerra e ela fica na URL. Aceito com o teste rodando.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          },
          {
            "name": "varianteID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "desvinculada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "encerrou": {
                      "type": "boolean"
                    },
                    "nova_principal": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.ApagarVariante",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/ab/promover": {
      "post": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Promover uma variante (e opcionalmente iniciar o teste)",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "variante_id"
                ],
                "properties": {
                  "variante_id": {
                    "type": "string"
                  },
                  "iniciar": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "description": "Com `iniciar: true`, inicia o teste (exige pesos válidos). Sem ele, ENCERRA com a vencedora e, se ela é uma página-variante, ela ASSUME A URL do teste (a principal antiga segue como página normal), na mesma transação (ADR-0095).",
        "responses": {
          "200": {
            "description": "promovida"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "409": {
            "description": "já há experimento rodando",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "variante inválida",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.Promover",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/experimento/pesos": {
      "put": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Definir a divisão de tráfego inteira, atomicamente (ADR-0093)",
        "description": "Um peso por variante existente, inteiros 0–100 somando exatamente 100 e ao menos duas com peso ≥ 1. Aceito com o teste rodando ou pausado (a atribuição é fixada por visitante: o peso novo vale para visitantes novos); 409 encerrado.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pesos"
                ],
                "additionalProperties": false,
                "properties": {
                  "pesos": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "divisão gravada"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "experimento encerrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "soma ≠ 100, variante faltando/a mais, peso fora de 0–100 ou menos de duas ativas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.DefinirPesos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/ab/pausar": {
      "post": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Pausar o teste (rodando → pausado; todo visitante volta à página publicada)",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "pausado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "o teste não está rodando",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/paginas/{id}/ab/pausar — rodando → pausado.\n\nOnde é usada: botão \"Pausar\" da tela de A/B.\n\nEfeitos: uma escrita; **todo visitante volta a ver a página publicada**. A amostra fica congelada (o runtime só conta quem viu uma variante) e retomar continua a mesma medição: `iniciado_em` não muda.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.Pausar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/ab/retomar": {
      "post": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Retomar um teste pausado (mesma medição; o início não muda)",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "rodando"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "o teste não está pausado, ou conflito (outro rodando / roteamento)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "variantes inválidas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/paginas/{id}/ab/retomar — pausado → rodando.\n\nOnde é usada: botão \"Retomar\" da tela de A/B.\n\nEfeitos: uma escrita; a distribuição volta para os visitantes novos, e quem já tinha braço fixado continua nele. Valida antes, como ao iniciar: com o teste pausado os braços puderam mudar.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.Retomar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/ab/desvincular": {
      "post": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Desvincular a PRÓPRIA página do teste em que ela está (lista de páginas)",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "desvinculada",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "encerrou": {
                      "type": "boolean"
                    },
                    "nova_principal": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/paginas/{id}/ab/desvincular — `{id}` é a PRÓPRIA página (a variante ou a principal), como a lista de páginas a conhece.\n\nOnde é usada: ação \"Desvincular do teste\" da lista de páginas.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.DesvincularPagina",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/ab-vinculos": {
      "get": {
        "tags": [
          "Testes A/B"
        ],
        "summary": "Páginas da conta em teste A/B não encerrado (selo da lista)",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "vinculos": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/paginas/ab-vinculos — para o selo \"Teste A/B: <url> · <peso>%\" e o agrupamento na lista de páginas.\n\nOnde é usada: lista de páginas do painel.\n\nEfeitos: uma leitura sob o tenant da sessão.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hx.Vinculos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/roteamento": {
      "get": {
        "tags": [
          "Roteamento"
        ],
        "summary": "Obter a regra de roteamento da página",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Roteamento"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/paginas/{id}/experimento.\n\nOnde é usada: tela de A/B do painel.\n\nEfeitos: uma leitura. Página SEM experimento responde 200 com o corpo vazio de variantes, e não 404: \"esta página não tem teste\" é uma resposta sobre o mundo, e a tela precisa dela para mostrar o botão de criar. 404 mandaria a tela tratar o caso normal como erro.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Roteamento"
        ],
        "summary": "Salvar a regra de roteamento",
        "description": "Exclusivo com o experimento A/B (ADR-0014). Máximo de 20 destinos.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Roteamento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "salva"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "conflita com um experimento ativo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "estratégia/destinos inválidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Salvar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Roteamento"
        ],
        "summary": "Apagar a regra de roteamento",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          }
        ],
        "responses": {
          "204": {
            "description": "apagada"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/dominios/{id}.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. Apagar TIRA a autorização de TLS, e é o único caminho para isso — de propósito: um domínio verificado que pudesse ser \"desverificado\" por outro caminho sairia do ar sem ninguém entender por quê.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/cliques": {
      "get": {
        "tags": [
          "Roteamento"
        ],
        "summary": "Cliques por destino de roteamento",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaID"
          },
          {
            "name": "dias",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "janela em dias"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cliques": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/redirects/{id}/cliques.\n\nOnde é usada: relatório da tela de links curtos.\n\nEfeitos: uma leitura. Devolve o CONTADOR, e não uma lista de eventos: a pergunta é sempre \"quantos\", nunca \"quais\". Uma linha por clique transformaria o caminho mais quente do redirect num INSERT por requisição.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Cliques",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/midias": {
      "get": {
        "tags": [
          "Mídia"
        ],
        "summary": "Listar a galeria da conta",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "teto de itens"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "midias": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "uma página da trilha (`id desc`) com os filtros da query.\n\nSaídas: 200 `{itens, proximo_cursor}` (\"\" = fim); 422 filtro inválido (cursor, limite, de/ate fora de RFC3339); 500 falha de leitura.\n\nEfeitos: só leitura.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "mds.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Mídia"
        ],
        "summary": "Enviar uma imagem (multipart)",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "description": "Envie o arquivo no campo `arquivo` de um `multipart/form-data`. O arquivo passa por antivírus antes de ser gravado: infectado = 422; antivírus configurado e fora do ar = 503 com `Retry-After` (fail-closed — tente de novo).",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "arquivo"
                ],
                "properties": {
                  "arquivo": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "já existia (deduplicada por conteúdo)"
          },
          "201": {
            "description": "enviada"
          },
          "400": {
            "description": "campo `arquivo` ausente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "413": {
            "description": "arquivo grande demais",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "arquivo recusado pelo antivírus",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "description": "chave indisponível OU antivírus sem responder (reenvie; vem `Retry-After`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "mds.Upload",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/midias/de-url": {
      "post": {
        "tags": [
          "Mídia"
        ],
        "summary": "Importar uma imagem por URL",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "description": "Baixa a imagem da URL (com defesa contra SSRF). Desligado quando a importação por URL não está configurada (503).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "nome": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "já existia"
          },
          "201": {
            "description": "importada"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "422": {
            "description": "URL inválida / bloqueada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/pages_LimiteDeChaveAutoria"
          },
          "503": {
            "description": "importação por URL desligada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "mds.DeURL",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/midias/{id}": {
      "delete": {
        "tags": [
          "Mídia"
        ],
        "summary": "Apagar uma imagem (soft delete — o arquivo fica; o suporte restaura)",
        "description": "Some da galeria e do endereço público, mas a linha e o ARQUIVO (original e variantes) permanecem no disco; o painel master restaura (ADR-0112). SÓ sessão.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "apagada (soft)"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "mds.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/public/midias/{id}": {
      "get": {
        "tags": [
          "Mídia"
        ],
        "summary": "Servir uma imagem publicada (anônimo)",
        "description": "Rota PÚBLICA — quem pede é o navegador de um visitante numa página no ar, sem sessão. Devolve os bytes da imagem.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "os bytes da imagem",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "mídia não encontrada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "mds.Publica",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/public/midias/{id}/w/{largura}": {
      "get": {
        "tags": [
          "Mídia"
        ],
        "summary": "Servir uma variante redimensionada da imagem (anônimo)",
        "description": "Rota PÚBLICA usada pelo `srcset` das páginas publicadas. Reduz a imagem para a largura pedida (nunca amplia; proporção e orientação EXIF preservadas) e a entrega em WebP quando o `Accept` anuncia `image/webp` (senão JPEG/PNG). A largura é FECHADA numa allowlist — qualquer outro valor é 404, sem tocar no banco nem no disco. Gerada uma vez e guardada; resposta com `Cache-Control: public, max-age=31536000, immutable`, `Vary: Accept`, `X-Content-Type-Options: nosniff` e ETag por (mídia, largura, formato). GIF, SVG, AVIF e imagens acima de 40 megapixels respondem o ORIGINAL (mesmos cabeçalhos da rota do original, inclusive a CSP do SVG).",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "largura",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "enum": [
                160,
                320,
                480,
                640,
                960,
                1280,
                1920
              ]
            }
          },
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Com `image/webp`, a variante sai em WebP."
          }
        ],
        "responses": {
          "200": {
            "description": "os bytes da variante (ou do original",
            "ver descrição)": null,
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "largura fora da allowlist",
            "ou mídia não encontrada": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "mds.PublicaVariante",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/redirects": {
      "get": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Listar links curtos",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redirects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Redirect"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "uma página da trilha (`id desc`) com os filtros da query.\n\nSaídas: 200 `{itens, proximo_cursor}` (\"\" = fim); 422 filtro inválido (cursor, limite, de/ate fora de RFC3339); 500 falha de leitura.\n\nEfeitos: só leitura.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Criar link curto",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Redirect"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Redirect"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "409": {
            "description": "slug já em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "destino/slug inválidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/dominios — valida (422 para inválido/reservado, sufixo público ou sob a base dos grátis), classifica pela PSL, confere a cota da classe (402 `{recurso, limite, usado}`; 503 sem plataforma) e grava.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. # O domínio nasce NÃO verificado E é isso que impede o pior desfecho do produto: sem a prova de DNS, qualquer conta declararia `banco.com.br` e passaria a receber certificado TLS para esse nome — servindo uma página de captura no domínio de outra pessoa, com cadeado verde.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/redirects/{id}": {
      "get": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Obter um link curto",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Redirect"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/paginas/{id}/experimento.\n\nOnde é usada: tela de A/B do painel.\n\nEfeitos: uma leitura. Página SEM experimento responde 200 com o corpo vazio de variantes, e não 404: \"esta página não tem teste\" é uma resposta sobre o mundo, e a tela precisa dela para mostrar o botão de criar. 404 mandaria a tela tratar o caso normal como erro.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Atualizar um link curto",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Redirect"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Redirect"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "slug já em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PUT /v1/formularios/{id}.\n\nOnde é usada: edição.\n\nEfeitos: escreve a linha.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Apagar um link curto",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "apagado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/dominios/{id}.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. Apagar TIRA a autorização de TLS, e é o único caminho para isso — de propósito: um domínio verificado que pudesse ser \"desverificado\" por outro caminho sairia do ar sem ninguém entender por quê.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/redirects/{id}/cliques": {
      "get": {
        "tags": [
          "Links curtos"
        ],
        "summary": "Contagem de cliques do link",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "cliques": {
                      "type": "integer",
                      "format": "int64"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/redirects/{id}/cliques.\n\nOnde é usada: relatório da tela de links curtos.\n\nEfeitos: uma leitura. Devolve o CONTADOR, e não uma lista de eventos: a pergunta é sempre \"quantos\", nunca \"quais\". Uma linha por clique transformaria o caminho mais quente do redirect num INSERT por requisição.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hr.Cliques",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/ia/gerar-pagina": {
      "post": {
        "tags": [
          "Geração por IA"
        ],
        "summary": "Gerar um rascunho de página por IA",
        "description": "Produz um rascunho a partir de um pedido em linguagem natural — SEMPRE um documento v2 (sem cabeçalho forçado, sem `form_id`: o formulário é escolhido no editor). O `html` que o modelo devolver é REMOVIDO e listado em `removidos`; item fora do contrato é descartado. NÃO grava nada. Responde 503 quando o provedor de IA não está configurado neste ambiente.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pedido"
                ],
                "properties": {
                  "pedido": {
                    "type": "string",
                    "description": "descrição da página desejada"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "rascunho gerado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "template"
                  ],
                  "properties": {
                    "template": {
                      "$ref": "#/components/schemas/pages_DocumentoV2"
                    },
                    "removidos": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      },
                      "description": "tipos privilegiados removidos da saída do modelo (ex.: html)"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "422": {
            "description": "pedido vazio/longo demais",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "502": {
            "description": "o provedor falhou ao gerar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "geração por IA desligada neste ambiente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hg.Gerar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/campos-nativos": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_api"
        ],
        "summary": "GET /v1/campos-nativos → `{campos, grupos, paises}`.",
        "description": "GET /v1/campos-nativos → `{campos, grupos, paises}`.\n\nOnde é usada: construtor de formulário (paleta de campos nativos, seletor de país) e o diálogo de destinos (alvos do mapa). Sessão do painel exigida pela rota.\n\nEfeitos: nenhum — lê o catálogo embutido. Sem tenant na pergunta: o catálogo é o mesmo para toda conta. O 401 sem conta na sessão é só coerência com as rotas vizinhas.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "handlers.CamposNativos",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/dominios": {
      "get": {
        "tags": [
          "Domínios"
        ],
        "summary": "Listar domínios da conta",
        "description": "Sessão ou API key com `pages:read`. O domínio **padrão** da conta (`apages.xyz`, criado automaticamente) vem **primeiro** e traz `padrao: true`; os demais seguem com `padrao: false`.",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "ok (o padrão primeiro)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dominios": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Dominio"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hd.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Domínios"
        ],
        "summary": "Adicionar domínio (gera o TXT de posse)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_CriarDominio"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Dominio"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "402": {
            "description": "limite do plano atingido para a classe do domínio",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_CotaExcedida"
                }
              }
            }
          },
          "409": {
            "description": "host já cadastrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "host inválido, reservado, sufixo público ou sob o domínio dos grátis",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "limite do plano indisponível para conferir agora",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/dominios — valida (422 para inválido/reservado, sufixo público ou sob a base dos grátis), classifica pela PSL, confere a cota da classe (402 `{recurso, limite, usado}`; 503 sem plataforma) e grava.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. # O domínio nasce NÃO verificado E é isso que impede o pior desfecho do produto: sem a prova de DNS, qualquer conta declararia `banco.com.br` e passaria a receber certificado TLS para esse nome — servindo uma página de captura no domínio de outra pessoa, com cadeado verde.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hd.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dominios/{id}/pagina-inicial": {
      "put": {
        "tags": [
          "Domínios"
        ],
        "summary": "Definir a página inicial (home/apex) do domínio",
        "description": "Sessão ou API key com `pages:write`. Marca uma página, já publicada ou não, como a home deste domínio — `GET https://host/` passa a resolver nela (subdomínio `*.apages.xyz` ou domínio próprio). A página precisa ser da mesma conta. **Se ela estiver em OUTRO endereço (outro domínio ou a plataforma), é movida para este domínio mantendo o slug** — o endereço antigo passa a responder 301 se ela já esteve no ar — e então vira a inicial. Trocar a inicial não apaga nada: a anterior continua viva e servindo no próprio slug. Não publicada é aceita (`publicada:false` na resposta; `/` segue 404 até publicar). Para tirar a inicial, use o DELETE: `/` volta ao comportamento anterior (404 sem inicial).",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_DefinirPaginaInicial"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "domínio com a home atualizada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Dominio"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/pages_PedidoInvalidoAutoria"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "description": "domínio OU página não encontrados (ids distintos e indistinguíveis de outra conta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "409": {
            "description": "`motivo`: `pagina_inicial` (a página é a home de OUTRO domínio; troque a de lá antes) ou `caminho_em_uso` (o slug já é de outra página deste domínio)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_MoverDominioErro"
                }
              }
            }
          },
          "422": {
            "description": "`motivo`: `dominio_nao_verificado` ou `caminho_invalido` (ao mover a página para este domínio)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_MoverDominioErro"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hd.DefinirPaginaInicial",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Domínios"
        ],
        "summary": "Remover a página inicial do domínio",
        "description": "Sessão ou API key com `pages:write`. Idempotente — domínio já sem home marcada continua respondendo 200. Nenhuma página é apagada nem movida.",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "domínio com pagina_inicial: null",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Dominio"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hd.LimparPaginaInicial",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dominios/{id}/verificar": {
      "post": {
        "tags": [
          "Domínios"
        ],
        "summary": "Verificar os 3 passos (TXT, apontamento e certificado)",
        "description": "Confere a posse (TXT, se ainda não provada), o apontamento (CNAME/A para a borda) e, com os dois ok, faz um handshake TLS que aquece o certificado. Responde 200 com o estado de CADA passo (um passo reprovado vem com `ok:false` e o motivo), e 503 só quando o DNS não respondeu a nada.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "estado dos 3 passos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Dominio"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "description": "DNS indisponível para checar agora",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hd.Verificar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/dominios/{id}": {
      "delete": {
        "tags": [
          "Domínios"
        ],
        "summary": "Remover domínio",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "removido"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/dominios/{id}.\n\nOnde é usada: tela de domínios.\n\nEfeitos: uma escrita. Apagar TIRA a autorização de TLS, e é o único caminho para isso — de propósito: um domínio verificado que pudesse ser \"desverificado\" por outro caminho sairia do ar sem ninguém entender por quê.",
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hd.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/{id}/dominio": {
      "put": {
        "tags": [
          "Páginas"
        ],
        "summary": "Mover a página de domínio e/ou de caminho",
        "description": "Sessão ou API key com `pages:write`. Troca o endereço público da página: `dominio_id` é o id do domínio da conta (`GET /v1/dominios`); `\"\"` = host da plataforma. `caminho` é o slug de um segmento (`^[a-z0-9]+(-[a-z0-9]+)*$`); `\"\"` mantém o slug atual. Se a página já foi publicada alguma vez, o **endereço antigo passa a responder 301** (308 em métodos que não GET/HEAD) para o novo. Repetir o mesmo corpo é idempotente (`alterado:false`). Página ou domínio inexistente, ou de outra conta, responde 404 (indistinguível).",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_MoverDominioPedido"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "movida (ou já estava lá)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_MoverDominioResposta"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "`motivo`: `caminho_em_uso` (o endereço já é de outra página) ou `pagina_inicial` (a página é a home do domínio de origem)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_MoverDominioErro"
                }
              }
            }
          },
          "422": {
            "description": "`motivo`: `dominio_nao_verificado` ou `caminho_invalido`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_MoverDominioErro"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Alterar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios": {
      "get": {
        "tags": [
          "Formulários"
        ],
        "summary": "Listar formulários",
        "description": "Aceita sessão ou API key com `forms:read`. Com `?external_id=` devolve só o formulário com esse id externo (lista vazia se não houver).",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "external_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "filtra pelo id do formulário no seu sistema"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "formularios": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Form"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Formulários"
        ],
        "summary": "Criar formulário",
        "description": "Sessão ou API key com `forms:write`. Envie `external_id` para amarrar o formulário ao do seu sistema (colisão → 409). Com o header `Idempotency-Key`, repetir a chamada (mesma chave e mesmo corpo) devolve 201 com o MESMO formulário e `Idempotent-Replayed: true`; a mesma chave com outro corpo responde 422. Para criar-ou-atualizar sem guardar o id do Asender, prefira `PUT /v1/formularios/externo/{external_id}`.",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_IdempotencyKeyForm"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Form"
              },
              "example": {
                "external_id": "blog-contato",
                "nome": "Contato do blog",
                "campos": [
                  {
                    "nome": "email",
                    "rotulo": "E-mail",
                    "tipo": "email",
                    "obrigatorio": true
                  },
                  {
                    "nome": "nome",
                    "rotulo": "Nome",
                    "tipo": "texto"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criado (ou replay idempotente — então vem `Idempotent-Replayed`)",
            "headers": {
              "Idempotent-Replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                },
                "description": "presente só quando a resposta é o replay de uma `Idempotency-Key` já usada"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Form"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "409": {
            "description": "nome ou `external_id` já em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "definição inválida, ou `Idempotency-Key` já usada com outro corpo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/externo/{external_id}": {
      "get": {
        "tags": [
          "Formulários"
        ],
        "summary": "Obter formulário pelo id externo",
        "description": "Sessão ou API key com `forms:read`. Procura entre os formulários não apagados da conta.",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_ExternalID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Form"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.ObterExterno",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Formulários"
        ],
        "summary": "Criar ou atualizar formulário pelo id externo (upsert idempotente)",
        "description": "Sessão ou API key com `forms:write`. Se não existe formulário com esse `external_id` na conta, cria (201); se existe, SUBSTITUI a definição (200), como o `PUT /v1/formularios/{id}`. Rodar de novo com o mesmo corpo não muda nada. O `external_id` do path é o que vale.",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_ExternalID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Form"
              },
              "example": {
                "nome": "Contato do blog",
                "exige_consentimento": true,
                "texto_consentimento": "Concordo em receber comunicações",
                "base_legal": "consentimento",
                "campos": [
                  {
                    "nome": "email",
                    "rotulo": "E-mail",
                    "tipo": "email",
                    "obrigatorio": true
                  },
                  {
                    "nome": "nome",
                    "rotulo": "Nome",
                    "tipo": "texto",
                    "obrigatorio": true
                  },
                  {
                    "nome": "empresa",
                    "chave": "empresa",
                    "rotulo": "Empresa",
                    "tipo": "texto"
                  },
                  {
                    "nome": "interesse",
                    "rotulo": "Interesse",
                    "tipo": "opcao",
                    "opcoes": [
                      "produto",
                      "parceria"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "atualizado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Form"
                }
              }
            }
          },
          "201": {
            "description": "criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Form"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "409": {
            "description": "nome já em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "definição inválida",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.PutExterno",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/arquivados": {
      "get": {
        "tags": [
          "Formulários"
        ],
        "summary": "Listar os formulários arquivados da conta (lixeira)",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:read"
            ]
          }
        ],
        "description": "Lixeira do usuário (ADR-0112): os formulários que a conta apagou, do mais recente para o mais antigo (até 200). Só lê; para trazer um de volta use `POST /v1/formularios/{id}/desarquivar`. Lista vazia = `{\"formularios\": []}`.",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "formularios"
                  ],
                  "properties": {
                    "formularios": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_FormularioArquivado"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.ListarArquivados",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/{id}/desarquivar": {
      "post": {
        "tags": [
          "Formulários"
        ],
        "summary": "Desarquivar um formulário",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:write"
            ]
          }
        ],
        "description": "Sessão ou API key com `forms:write`. Devolve o formulário apagado à lista e o runtime volta a aceitar submissões dele; definição, destinos e leads estão intactos. Registra auditoria (`restaurar`). Formulário inexistente, de outra conta ou não arquivado responde 404 (indistinguível, nunca 403). Se outro formulário vivo ocupa um índice único (ex.: `external_id`), responde 409 e nada é sobrescrito.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "desarquivado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "outro formulário ativo ocupa o `external_id` (ou outro índice único)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "codigo",
                    "erro"
                  ],
                  "properties": {
                    "codigo": {
                      "type": "string",
                      "enum": [
                        "external_id_em_uso",
                        "conflito"
                      ]
                    },
                    "erro": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.Desarquivar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/{id}": {
      "get": {
        "tags": [
          "Formulários"
        ],
        "summary": "Obter formulário (com o snippet de embed e o rascunho do editor)",
        "description": "Sessão ou API key com `forms:read`. Formulário de outra conta responde 404.",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_FormDetalhe"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Formulários"
        ],
        "summary": "Atualizar (publicar) formulário",
        "description": "Substitui o recurso inteiro — inclusive o layout — e ZERA o rascunho do editor na mesma escrita. É o \"Publicar\" do editor. Sessão ou API key com `forms:write`. `external_id` ausente mantém o atual; presente e diferente troca, se estiver livre (409 se em uso).",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Form"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Form"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "nome ou `external_id` já em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "definição inválida",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.Atualizar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Formulários"
        ],
        "summary": "Apagar formulário",
        "description": "Sessão ou API key com `forms:write`. Apagar é lógico: o `external_id` fica livre para um novo formulário.",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "forms:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "apagado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "$ref": "#/components/responses/pages_ChaveIndisponivelAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/agendas": {
      "get": {
        "tags": [
          "Formulários"
        ],
        "summary": "Listar as agendas da conta (seletor do campo Agendamento)",
        "security": [
          {
            "sessao": []
          }
        ],
        "description": "Proxy de `GET /interno/v1/agendas` do serviço Agenda, com a conta da sessão. Alimenta o seletor do campo `agendamento` do construtor. SÓ sessão (chave de API responde 401). Serviço Agenda fora do ar ou não configurado: 503 `application/problem+json` (RFC 9457, `type` `…/agenda-indisponivel`).",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "agendas"
                  ],
                  "properties": {
                    "agendas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_AgendaResumo"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "503": {
            "description": "serviço Agenda indisponível",
            "content": {
              "application/problem+json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "type",
                    "title",
                    "status"
                  ],
                  "properties": {
                    "type": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "status": {
                      "type": "integer",
                      "example": 503
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.ListarAgendas",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/{id}/destinos": {
      "get": {
        "tags": [
          "Formulários"
        ],
        "summary": "Listar destinos do formulário (crm/marketing/webhook)",
        "description": "Os destinos para onde a submissão é copiada, com o mapa de campos de cada um. Anti-IDOR: um formulário de outra conta responde 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok — os destinos (com `condicao_erro` nos quebrados) e se a conta tem o Marketing. Formulário com caixa de aceite numa conta com Marketing ganha, UMA vez, o destino Marketing com a condição \"caixa marcada\" (preset).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "destinos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Destino"
                      }
                    },
                    "marketing": {
                      "type": "object",
                      "properties": {
                        "disponivel": {
                          "type": "boolean"
                        },
                        "motivo_indisponivel": {
                          "type": "string",
                          "enum": [
                            "",
                            "sem_produto",
                            "nao_confirmado"
                          ]
                        }
                      }
                    },
                    "integracoes": {
                      "type": "object",
                      "description": "se o serviço de Integrações respondeu (false = sem resumo nos destinos integracao)",
                      "properties": {
                        "disponivel": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.ListarDestinos",
        "x-asender-fonte": "spec+codigo"
      },
      "put": {
        "tags": [
          "Formulários"
        ],
        "summary": "Substituir a lista de destinos do formulário",
        "description": "SUBSTITUI a lista inteira (como campos/origens). Valida tipo, mapa e a CONDIÇÃO de cada destino contra os campos atuais (campo inexistente, operador do tipo errado, opção que não existe → 422); Marketing só com \"aceite marcado\"; webhook exige URL http(s). O SSRF do webhook é conferido no runtime, no instante do despacho. Destino integracao NOVO ou ALTERADO é conferido no serviço de Integrações (instalação existe e é da conta; opções valem) — 422 com `codigo` (IntegracaoRepetida, LimiteIntegracoes, IntegracaoInvalida, IntegracaoInexistente, OpcaoInvalida) e `local`; serviço fora → 503 IntegracoesIndisponivel só nesse caso.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "destinos": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/pages_Destino"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "destinos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Destino"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "422": {
            "description": "destino ou condição inválidos (integracao: + codigo e local)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "IntegracoesIndisponivel — só com destino integracao novo/alterado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.DefinirDestinos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/{id}/rascunho": {
      "put": {
        "tags": [
          "Formulários"
        ],
        "summary": "Salvar o rascunho do editor (sem publicar)",
        "description": "Guarda o corpo do PUT do formulário SEM publicá-lo — o formulário no ar não muda. Só a FORMA é conferida (campos desconhecidos → 400, teto de 512 KiB → 413): rascunho pode estar incompleto; as regras rodam no publicar. Anti-IDOR: formulário de outra conta responde 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Form"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "salvo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rascunho_em": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "413": {
            "description": "rascunho acima do teto",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.SalvarRascunho",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Formulários"
        ],
        "summary": "Descartar o rascunho do editor",
        "description": "Idempotente; o formulário no ar não muda. Formulário de outra conta responde 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "descartado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.DescartarRascunho",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/gerar-com-ia": {
      "post": {
        "tags": [
          "Formulários"
        ],
        "summary": "Criar um formulário a partir de uma descrição (IA)",
        "description": "Pede a um modelo de IA o rascunho dos campos e CRIA o formulário na conta da sessão. O servidor nunca confia na saída: parse estrito, só tipos e propriedades conhecidos (nada de destino, webhook, origens, HTML ou consentimento), chave do catálogo nativo só com tipo compatível, `opcao` com ao menos uma opção (senão o campo é descartado), teto de campos e `Form.Validar`. O formulário nasce com os padrões seguros (consentimento exigido, sem destinos, sem origens) e NÃO é publicado nem ligado a nenhum destino. O que foi descartado ou ajustado volta em `avisos`. SÓ sessão (chave de API responde 401). Limite: 10 por minuto por conta (429). 503 quando a IA não está configurada (a chave é cadastrada em Backoffice > Integrações).",
        "security": [
          {
            "sessao": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "descricao"
                ],
                "additionalProperties": false,
                "properties": {
                  "descricao": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "o formulário desejado, em linguagem natural"
                  },
                  "idioma": {
                    "type": "string",
                    "enum": [
                      "pt",
                      "pt-BR",
                      "en",
                      "es"
                    ],
                    "description": "idioma dos rótulos (padrão pt-BR)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "formulário criado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "nome",
                    "avisos"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "nome": {
                      "type": "string"
                    },
                    "avisos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "campo",
                          "motivo"
                        ],
                        "properties": {
                          "campo": {
                            "type": "string"
                          },
                          "motivo": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido ou com campo desconhecido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "nome em uso mesmo após os sufixos automáticos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "descrição vazia/acima de 2000 caracteres ou idioma não suportado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "description": "limite por minuto da conta ou teto de gerações do provedor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "502": {
            "description": "o provedor falhou ou a saída veio fora do formato",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "IA não configurada neste ambiente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hf.GerarComIA",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/cotas": {
      "get": {
        "tags": [
          "Domínios"
        ],
        "summary": "Uso e teto de domínios da conta, pelo plano",
        "description": "`pages_subdominios` = grátis (*.apages.xyz) + subdomínios próprios; `pages_dominios_principais` = domínios próprios apex. `limite: null` = ilimitado. O teto vem do plano (plataforma, cache 60 s); sem resposta dela, 503 — nunca \"0 de 0\". Tenant da sessão, nunca da query.",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pages_subdominios": {
                      "$ref": "#/components/schemas/pages_UsoDeCota"
                    },
                    "pages_dominios_principais": {
                      "$ref": "#/components/schemas/pages_UsoDeCota"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "503": {
            "description": "plataforma indisponível para conferir o plano",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Cotas",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/subdominios-gratis/bases": {
      "get": {
        "tags": [
          "Domínios"
        ],
        "summary": "Listar as bases em que um subdomínio grátis pode ser reservado",
        "description": "As bases grátis da plataforma (`apages.xyz` e as que forem disponibilizadas). `padrao` é a primária, usada quando `base` não é enviada.",
        "responses": {
          "200": {
            "description": "bases e a padrão",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "bases",
                    "padrao"
                  ],
                  "properties": {
                    "bases": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "example": "apages.xyz"
                      }
                    },
                    "padrao": {
                      "type": "string",
                      "example": "apages.xyz"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Bases",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/subdominios-gratis/disponivel": {
      "get": {
        "tags": [
          "Domínios"
        ],
        "summary": "Conferir se um nome grátis (<rotulo>.<base>) pode ser reservado",
        "description": "Rótulo DNS de 3 a 63 caracteres [a-z0-9-], sem punycode; fora dos nomes reservados da plataforma e das marcas/termos sensíveis (inclusive imitações como `1tau`/`paypa1`). `em_uso` não diz de qual conta. Teto de 30 consultas por minuto por conta (429).",
        "parameters": [
          {
            "name": "rotulo",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "minhaloja"
            }
          },
          {
            "name": "base",
            "in": "query",
            "required": false,
            "description": "Base grátis (de GET /v1/subdominios-gratis/bases); vazia = a padrão. Fora da lista: 422 base_desconhecida.",
            "schema": {
              "type": "string",
              "example": "apages.xyz"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "resposta (disponível ou o motivo)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "disponivel"
                  ],
                  "properties": {
                    "disponivel": {
                      "type": "boolean"
                    },
                    "host": {
                      "type": "string",
                      "example": "minhaloja.apages.xyz"
                    },
                    "motivo": {
                      "type": "string",
                      "enum": [
                        "invalido",
                        "reservado",
                        "sensivel",
                        "em_uso"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "422": {
            "description": "base desconhecida (`motivo: base_desconhecida`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "description": "consultas demais",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Disponivel",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/subdominios-gratis": {
      "post": {
        "tags": [
          "Domínios"
        ],
        "summary": "Reservar um subdomínio grátis",
        "description": "Nasce verificado e pronto (posse da plataforma, TLS pelo certificado curinga). Conta no recurso `pages_subdominios` (que conta SÓ os grátis; host do cliente, como `bio.cliente.com.br`, conta em `pages_dominios_principais`). `base` opcional escolhe a base grátis (vazia = a padrão). Liberar é `DELETE /v1/dominios/{id}`. Até 10 reservas/trocas por hora por conta (429).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rotulo"
                ],
                "properties": {
                  "rotulo": {
                    "type": "string",
                    "example": "minhaloja"
                  },
                  "base": {
                    "type": "string",
                    "example": "apages.xyz",
                    "description": "Opcional; vazia = a base padrão. Desconhecida: 422 base_desconhecida."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "reservado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_DominioGratis"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "402": {
            "description": "cota do plano esgotada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_CotaExcedida"
                }
              }
            }
          },
          "409": {
            "description": "nome em uso (ou em quarentena)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "rótulo recusado (com `motivo`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "description": "reservas demais na última hora",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "plataforma indisponível para conferir o plano (fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/subdominios-gratis/{id}/trocar": {
      "post": {
        "tags": [
          "Domínios"
        ],
        "summary": "Trocar o nome de um subdomínio grátis (páginas e links vão junto)",
        "description": "O nome antigo fica 30 dias indisponível para outras contas. Grátis de outra conta, ou id que não é grátis, responde 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rotulo"
                ],
                "properties": {
                  "rotulo": {
                    "type": "string"
                  },
                  "base": {
                    "type": "string",
                    "description": "Opcional; vazia = a base padrão."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "trocado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_DominioGratis"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "404": {
            "$ref": "#/components/responses/pages_NaoEncontradoAutoria"
          },
          "409": {
            "description": "nome em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "422": {
            "description": "rótulo recusado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "description": "trocas demais na última hora",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "h.Trocar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/paginas/lote": {
      "post": {
        "tags": [
          "Páginas"
        ],
        "summary": "Ação em massa sobre páginas (publicar, despublicar, duplicar, mudar domínio, arquivar)",
        "security": [
          {
            "sessao": []
          },
          {
            "apiKey": [
              "pages:write"
            ]
          },
          {
            "apiKey": [
              "pages:publish"
            ]
          }
        ],
        "description": "Executa UMA ação sobre até 100 páginas, cada uma de forma independente (sem tudo-ou-nada): o corpo da resposta traz o desfecho POR PÁGINA (`ok`, `pulada` ou `erro`, com `motivo`). A conta vem da sessão/chave; cada id é resolvido dentro dela e um id de outra conta é reportado como `nao_encontrada`, indistinguível de um id inexistente. `simular: true` devolve o que aconteceria, sem escrever nada. **publicar** publica a última versão SALVA e é idempotente (já no ar = `ok` com `sem_mudanca`). Conteúdo recusado pelo contrato de domínio é `pulada/conteudo_invalido`. Se houver rascunho de servidor mais novo que a última versão, a política `rascunho` decide: `pular` (padrão — `pulada/rascunho_mais_novo`) ou `publicar_salva` (publica a última versão salva; o rascunho NUNCA vira versão nem é descartado). **despublicar** tira do ar sem tocar em versões nem rascunho (idempotente). **duplicar** cria uma cópia despublicada da última versão salva (`-copia`, `-copia-2`…), com a mesma guarda de bloco privilegiado da criação. **mudar_dominio** delega ao serviço de mudança de domínio (mesmo contrato de `PUT /v1/paginas/{id}/dominio`, incluindo o 301 do endereço antigo); `dominio_id` é obrigatório (`\"\"` = host da plataforma) e `caminho_por_pagina` opcional (ausente = mantém o caminho atual). Cada página processada grava um log de auditoria (`lote_id`, conta, usuário, ação, desfecho). Limite de ritmo: 12 chamadas por minuto por conta (429). **arquivar** é o soft delete da página (despublica e esconde; versões e rascunho ficam; o master restaura — ADR-0112). SÓ sessão: chave de API é recusada (403). **API key:** `publicar` e `despublicar` exigem `pages:publish`; `duplicar` e `mudar_dominio` exigem `pages:write` (sem o escopo da ação, 403).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_PedidoLote"
              },
              "examples": {
                "publicar": {
                  "value": {
                    "acao": "publicar",
                    "ids": [
                      "pag_01",
                      "pag_02"
                    ],
                    "rascunho": "pular"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "processado; veja `resultados` página a página",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_RespostaLote"
                }
              }
            }
          },
          "400": {
            "description": "corpo inválido (campo desconhecido",
            "inclusive tenant_id)": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoAutoria"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoAutoria"
          },
          "422": {
            "description": "ação inválida, 0 ou mais de 100 ids, política de rascunho inválida ou `dominio_id` ausente em mudar_dominio",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "429": {
            "description": "acima de 12 chamadas por minuto na conta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          },
          "503": {
            "description": "ações em massa indisponíveis (sem banco)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroAutoria"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_api",
        "x-asender-handler": "hp.Lote",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/relatorio": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Relatório filtrado (totais, série diária e dimensão)",
        "description": "Atribuição por SESSÃO (primeiro valor preenchido da visita): a conversão que acontece depois da página de entrada herda a origem dela. A comparação com o período anterior é esta mesma rota com a janela anterior. A dimensão `formulario` agrupa por `extras.form` e só conta eventos `form_view`/`form_start`/`form_submit`; na dimensão `pagina`, o funil de formulário embutido em site externo sem pixel (`pagina_id` vazio, `extras.origem=embed`) aparece como `site externo: <host>`.",
        "parameters": [
          {
            "name": "dimensao",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pagina",
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "pais",
                "dispositivo",
                "formulario"
              ],
              "default": "pagina"
            },
            "description": "dimensão das linhas; desconhecida devolve 400 com a lista"
          },
          {
            "$ref": "#/components/parameters/pages_De"
          },
          {
            "$ref": "#/components/parameters/pages_Ate"
          },
          {
            "name": "pagina",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "utm_source",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "origem; `(vazio)` = só visitas sem origem"
          },
          {
            "name": "utm_medium",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "utm_campaign",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "pais",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "ISO-2 da borda"
          },
          {
            "name": "dispositivo",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "mobile",
                "tablet",
                "desktop",
                "(vazio)"
              ]
            }
          },
          {
            "name": "formulario",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "id público do formulário (`extras.form`); `(vazio)` = eventos sem formulário"
          },
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_RelatorioAnalytics"
                }
              }
            }
          },
          "400": {
            "description": "dimensão/filtro/janela inválidos (RFC3339, teto 92 dias)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "501": {
            "description": "relatório indisponível neste ambiente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "a.Relatorio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/forms/{formID}/submissions": {
      "post": {
        "tags": [
          "Captura de leads"
        ],
        "summary": "Enviar uma submissão de um formulário (modo API)",
        "description": "Igual a `POST /v1/leads`, com o `formID` (id do Asender) no path. Exige API key com `forms:write`.",
        "security": [
          {
            "apiKey": [
              "forms:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "formID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/pages_IdempotencyKeyLead"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_LeadAPI"
              },
              "example": {
                "campos": {
                  "email": "fulana@exemplo.test",
                  "nome": "Fulana"
                },
                "id_externo": "blog-sub-48213",
                "consentimento": true,
                "ocorrido_em": "2026-10-08T14:03:00Z",
                "origem": {
                  "url": "https://blog.exemplo.test/post/lancamento",
                  "utm_source": "newsletter",
                  "utm_medium": "email",
                  "utm_campaign": "lancamento"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "duplicada — o `id_externo` (ou `Idempotency-Key`) já foi recebido neste formulário; nada de novo gravado e o `lead_id` é o do envio original",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_LeadAceito"
                },
                "example": {
                  "ok": true,
                  "lead_id": "3f0c8f0e-6a52-4d0e-9d0b-1c6f4e2d7a10",
                  "duplicada": true,
                  "id_externo": "blog-sub-48213"
                }
              }
            }
          },
          "201": {
            "description": "lead criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_LeadAceito"
                },
                "example": {
                  "ok": true,
                  "lead_id": "3f0c8f0e-6a52-4d0e-9d0b-1c6f4e2d7a10",
                  "duplicada": false,
                  "id_externo": "blog-sub-48213"
                }
              }
            }
          },
          "400": {
            "description": "recusado: corpo inválido, campo não declarado ou inválido, consentimento exigido ausente, `ocorrido_em` fora da janela",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                },
                "example": {
                  "error": {
                    "message": "o formulário exige consentimento"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoRuntime"
          },
          "404": {
            "description": "formulário não encontrado (ou de outra conta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "429": {
            "description": "limite por conta excedido (300 por minuto por padrão). Aguarde `Retry-After` segundos.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "segundos até poder tentar de novo"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "502": {
            "description": "não consegui registrar agora; tente de novo com o mesmo `id_externo`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "503": {
            "description": "não consegui validar a chave agora; tente de novo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.API",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/forms/externo/{externalID}/submissions": {
      "post": {
        "tags": [
          "Captura de leads"
        ],
        "summary": "Enviar uma submissão pelo id externo do formulário (modo API)",
        "description": "Igual a `POST /v1/forms/{formID}/submissions`, mas o formulário é achado pelo `external_id` que você definiu na autoria — você não precisa guardar o id do Asender. Exige API key com `forms:write`; formulário inexistente (ou de outra conta) responde 404.",
        "security": [
          {
            "apiKey": [
              "forms:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "externalID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9._:-]+$"
            },
            "description": "`external_id` do formulário"
          },
          {
            "$ref": "#/components/parameters/pages_IdempotencyKeyLead"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_LeadAPI"
              },
              "example": {
                "campos": {
                  "email": "fulana@exemplo.test",
                  "nome": "Fulana"
                },
                "id_externo": "blog-sub-48213",
                "consentimento": true,
                "ocorrido_em": "2026-10-08T14:03:00Z",
                "origem": {
                  "url": "https://blog.exemplo.test/post/lancamento",
                  "utm_source": "newsletter",
                  "utm_medium": "email",
                  "utm_campaign": "lancamento"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "duplicada — o `id_externo` (ou `Idempotency-Key`) já foi recebido neste formulário; nada de novo gravado e o `lead_id` é o do envio original",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_LeadAceito"
                },
                "example": {
                  "ok": true,
                  "lead_id": "3f0c8f0e-6a52-4d0e-9d0b-1c6f4e2d7a10",
                  "duplicada": true,
                  "id_externo": "blog-sub-48213"
                }
              }
            }
          },
          "201": {
            "description": "lead criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_LeadAceito"
                },
                "example": {
                  "ok": true,
                  "lead_id": "3f0c8f0e-6a52-4d0e-9d0b-1c6f4e2d7a10",
                  "duplicada": false,
                  "id_externo": "blog-sub-48213"
                }
              }
            }
          },
          "400": {
            "description": "recusado: corpo inválido, campo não declarado ou inválido, consentimento exigido ausente, `ocorrido_em` fora da janela",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                },
                "example": {
                  "error": {
                    "message": "o formulário exige consentimento"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "403": {
            "$ref": "#/components/responses/pages_SemEscopoRuntime"
          },
          "404": {
            "description": "formulário não encontrado (ou de outra conta)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "429": {
            "description": "limite por conta excedido (300 por minuto por padrão). Aguarde `Retry-After` segundos.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "segundos até poder tentar de novo"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "502": {
            "description": "não consegui registrar agora; tente de novo com o mesmo `id_externo`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "503": {
            "description": "não consegui validar a chave agora; tente de novo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.APIExterno",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/f/{formID}": {
      "post": {
        "tags": [
          "Captura de leads"
        ],
        "summary": "Enviar uma submissão pelo snippet (anônimo)",
        "description": "Modo embutido em site de terceiro. Sem credencial: a conta sai do próprio formulário, e a origem tem de estar na allowlist DELE (senão 403). Sempre responde 200 — inclusive quando a submissão é classificada como spam (aceitar e descartar são indistinguíveis, por desenho). Um `<form>` nativo (Accept: text/html) recebe a tela de agradecimento; um XHR recebe o JSON. Servido no runtime, com CORS por formulário. O `OPTIONS` é o preflight.",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "security": [],
        "parameters": [
          {
            "name": "formID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "campos": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "aceito (ou spam, indistinguível)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_LeadAceito"
                }
              }
            }
          },
          "400": {
            "description": "corpo malformado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "403": {
            "description": "origem não autorizada para este formulário"
          },
          "404": {
            "description": "formulário não encontrado"
          },
          "503": {
            "description": "não consegui resolver a conta do formulário agora"
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.Snippet",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/f/{formID}/carimbo": {
      "get": {
        "tags": [
          "Captura de leads"
        ],
        "summary": "Obter o carimbo de tempo anti-spam do snippet (anônimo)",
        "description": "Chamado pelo script do snippet ao carregar. Devolve um carimbo assinado (HMAC) do instante do pedido, que o snippet reenvia no campo oculto `_asender_carimbo` do `POST /f/{formID}`; o runtime mede o tempo de preenchimento com o PRÓPRIO relógio. Mesma allowlist de origem do POST (senão 403, sem CORS) e `Cache-Control: no-store`. Os campos `_asender_website` (honeypot), `_asender_carimbo` e `_asender_interacao` são reservados: lidos pelo anti-spam e nunca gravados como dado do lead. Sem carimbo, o tempo é tratado como não medido (sem penalidade).",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "security": [],
        "parameters": [
          {
            "name": "formID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "carimbo emitido",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "carimbo"
                  ],
                  "properties": {
                    "carimbo": {
                      "type": "string",
                      "description": "v1.<unix ms>.<HMAC-SHA256 base64url>",
                      "example": "v1.1726228800000.Q2FyaW1ib0V4ZW1wbG8"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "origem não autorizada para este formulário",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "404": {
            "description": "formulário não encontrado ou carimbo desligado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "503": {
            "description": "não consegui resolver a conta do formulário agora"
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.Carimbo",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/f/{formID}/embed.js": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "devolve o loader com o HTML atual do formulário embutido como string JSON.",
        "description": "devolve o loader com o HTML atual do formulário embutido como string JSON.\n\nOnde é usada: `<script src=\".../f/{id}/embed.js\" async>` colado no site do cliente (o `snippet_js` do painel).\n\nEfeitos: nenhum além da resposta. # Sem checagem de origem aqui `<script src>` não manda `Origin`, e o HTML não é segredo: é o mesmo que o modo HTML entrega para colar à vista. A política de origem continua onde age — no carimbo e no POST, que o HTML injetado chama. # JSON como literal JS `json.Marshal` escapa `<`, `>`, `&` e U+2028/U+2029: o HTML não fecha o `<script>` e não quebra a string. # Cache HTTP com revalidação (forms-rapidos, 2026-10-01) `max-age=60, stale-while-revalidate=600`: o navegador reusa a resposta por 1 min sem perguntar nada (a maioria das visitas de um mesmo site, no mesmo minuto, não paga NADA); depois disso, por até 10 min, ainda serve o que tem na hora enquanto revalida em paralelo (quem não implementa `stale-while-revalidate` cai para a validação normal, sem quebrar). Não é `max-age` longo e cego: o formulário muda quando o dono publica, e 1 min é curto o bastante para \"editei e já vale\" continuar parecendo imediato. `public` porque o HTML não é segredo (§ \"sem checagem de origem\" acima) nem varia por visitante — é o mesmo snippet pra qualquer um que peça aquele `formID`. `ETag` é o hash dos bytes já montados (`embedCache`, por formID) — forte (mesmo conteúdo byte a byte, mesmo valor) e barato de comparar: o Go só lê o `If-None-Match` do pedido, nunca remonta o snippet para decidir o 304. # Comparação FRACA, nunca `==` (achado em hml, 2026-10-01) A borda (nginx) comprime o `embed.js` on-the-fly e, ao fazer isso, REBAIXA o `ETag` forte que o runtime manda para fraco (`W/\"…\"`) — comportamento padrão do módulo de gzip dela, que não garante equivalência byte-a-byte entre o corpo comprimido e o cru. O navegador devolve esse `W/\"…\"` no `If-None-Match` da revisita; comparar com `==` contra o `etag` FORTE deste handler nunca batia, e toda revisita baixava o corpo inteiro de novo — a compressão/cache da rodada 1/2 chegavam a produção, só a comparação estava errada. `ifNoneMatchCasa` (RFC 9110 §13.1.2) ignora o prefixo `W/` de QUALQUER lado — é a única comparação válida para `If-None-Match` (a forte só existiria para `If-Match`/`Range`), então o mesmo código já cobre um Caddy que algum dia fizer o mesmo rebaixamento.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.EmbedJS",
        "x-asender-fonte": "codigo"
      }
    },
    "/f/{formID}/endereco": {
      "get": {
        "tags": [
          "Captura de leads"
        ],
        "summary": "Consultar o endereço de um CEP para o widget de endereço (anônimo)",
        "description": "Chamado pelo script do snippet quando o visitante completa o CEP. O runtime confere o formulário (tem endereço? aceita o país? o CEP tem o formato?) e consulta o `asender_plataforma_api` com a credencial de serviço — o core nunca fica exposto. Mesma política de origem do carimbo; teto de 0.5/s (rajada 10) por formulário × IP e 1/s (rajada 30) por IP. Nenhuma falha trava o formulário: o widget fica manual.",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "security": [],
        "parameters": [
          {
            "name": "formID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pais",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            },
            "example": "BR"
          },
          {
            "name": "cep",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 20
            },
            "example": "01310-100"
          }
        ],
        "responses": {
          "200": {
            "description": "endereço encontrado (sem número e complemento)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "pais",
                    "cep"
                  ],
                  "properties": {
                    "pais": {
                      "type": "string",
                      "example": "BR"
                    },
                    "cep": {
                      "type": "string",
                      "example": "01310-100"
                    },
                    "estado": {
                      "type": "string",
                      "example": "SP"
                    },
                    "estado_nome": {
                      "type": "string",
                      "example": "São Paulo"
                    },
                    "cidade": {
                      "type": "string",
                      "example": "São Paulo"
                    },
                    "bairro": {
                      "type": "string",
                      "example": "Bela Vista"
                    },
                    "rua": {
                      "type": "string",
                      "example": "Avenida Paulista"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "origem não autorizada para este formulário",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "404": {
            "description": "CEP não encontrado, formulário sem endereço ou consulta desligada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "422": {
            "description": "país fora dos aceitos ou CEP fora do formato do país",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "429": {
            "description": "consultas demais (Retry-After)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "503": {
            "description": "consulta indisponível agora (o endereço fica manual)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.Endereco",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/f/{formID}/scheduling/slots": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "mesma política de origem do `POST /f/{formID}` (sem `Origin` passa; com `Origin`, a allowlist do formulário) e o mesmo guard de conta (`tenantDoFormulario`, no roteador); CORS; teto por IP; janela de consulta; o campo no formulário; a janela do formulário; e o proxy.",
        "description": "mesma política de origem do `POST /f/{formID}` (sem `Origin` passa; com `Origin`, a allowlist do formulário) e o mesmo guard de conta (`tenantDoFormulario`, no roteador); CORS; teto por IP; janela de consulta; o campo no formulário; a janela do formulário; e o proxy.\n\nOnde é usada: o seletor de horário do snippet e da página. # Respostas 200 `{fuso, slots:[{inicio,fim}]}` (UTC, `Cache-Control: no-store`); 403 origem recusada; 404 formulário sem campo de agendamento; 410 formulário fechado (como a submissão); 422 janela inválida; 429 teto; 503 RFC 9457 `agenda-indisponivel`. Nenhuma trava o resto do formulário.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.HorariosDoAgendamento",
        "x-asender-fonte": "codigo"
      }
    },
    "/f/{formID}/agendamento/slots": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "HorariosDoAgendamentoLegado é o alias de `GET /f/{formID}/agendamento/slots` (ADR-0152 D3):",
        "description": "Onde é usada: JS do snippet antigo em cache em páginas de cliente.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.HorariosDoAgendamentoLegado",
        "x-asender-fonte": "codigo"
      }
    },
    "/f/{formID}/eventos": {
      "post": {
        "tags": [
          "Captura de leads"
        ],
        "summary": "Registrar eventos do funil do formulário embutido (anônimo)",
        "description": "Chamado pelo script do snippet via `navigator.sendBeacon` (corpo `text/plain` com JSON, para não disparar preflight). Tipos: impressao, visualizacao, inicio, campo, envio_tentativa, envio_ok, envio_erro. Mesma política de origem do carimbo; limite por formulário × IP. O IP não é gravado (o país vem do GeoIP). Alimenta `GET /v1/formularios/{formID}/analytics`.\nEspelho no Analytics geral: quando o formulário está num site externo SEM o pixel do Asender (`p` ausente) e não é do modo página (`m` ausente), visualizacao, inicio e envio_ok também viram `form_view`, `form_start` e `form_submit` em `analytics.eventos` (`pagina_id` vazio, `extras` = `{form, origem: \"embed\", host}`, `evento_id` `fe_<id>`), no tenant DONO do formulário. O `host` vem do `Origin`/`Referer`, nunca do corpo. Falha no espelho não afeta o funil nem a resposta (204).",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "security": [],
        "parameters": [
          {
            "name": "formID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "text/plain": {
              "schema": {
                "type": "object",
                "required": [
                  "s",
                  "e"
                ],
                "properties": {
                  "s": {
                    "type": "string",
                    "description": "sessão (UUID por carga de página)"
                  },
                  "d": {
                    "type": "string",
                    "description": "id do dispositivo (localStorage do site hospedeiro)"
                  },
                  "p": {
                    "type": "boolean",
                    "description": "o pixel do Asender está na página (script[data-lz]); com ele o servidor não espelha no Analytics geral"
                  },
                  "m": {
                    "type": "string",
                    "enum": [
                      "pagina"
                    ],
                    "description": "formulário do modo página (hospedado pelo Asender); não é espelhado"
                  },
                  "x": {
                    "type": "object",
                    "description": "contexto leve, só no primeiro lote da sessão",
                    "properties": {
                      "pagina": {
                        "type": "string"
                      },
                      "referrer": {
                        "type": "string"
                      },
                      "utm_source": {
                        "type": "string"
                      },
                      "utm_medium": {
                        "type": "string"
                      },
                      "utm_campaign": {
                        "type": "string"
                      },
                      "dispositivo": {
                        "type": "string",
                        "enum": [
                          "mobile",
                          "tablet",
                          "desktop"
                        ]
                      }
                    }
                  },
                  "e": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "object",
                      "required": [
                        "t"
                      ],
                      "properties": {
                        "t": {
                          "type": "string",
                          "enum": [
                            "impressao",
                            "visualizacao",
                            "inicio",
                            "campo",
                            "envio_tentativa",
                            "envio_ok",
                            "envio_erro"
                          ]
                        },
                        "c": {
                          "type": "string",
                          "description": "campo (em campo e envio_erro)"
                        },
                        "ms": {
                          "type": "integer",
                          "description": "ms desde a carga"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "s": "ccee201d-ba6e-4acc-b7a1-8a4e95ef5063",
                "d": "9998f6b3-8b64-4a15-8e2f-195b3ddf79bc",
                "e": [
                  {
                    "t": "impressao",
                    "ms": 0
                  },
                  {
                    "t": "visualizacao",
                    "ms": 1005
                  }
                ],
                "x": {
                  "dispositivo": "desktop",
                  "pagina": "cliente.test/lp"
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "eventos registrados"
          },
          "400": {
            "description": "lote inválido"
          },
          "403": {
            "description": "origem não autorizada para este formulário"
          },
          "404": {
            "description": "formulário não encontrado ou funil desligado"
          },
          "429": {
            "description": "limite de eventos excedido"
          },
          "503": {
            "description": "banco indisponível"
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Captura.Eventos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/events": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Eventos brutos",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaFiltro"
          },
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/pages_De"
          },
          {
            "$ref": "#/components/parameters/pages_Ate"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/analytics/events?pagina=&limite=.\n\nOnde é usada: tela de depuração da instrumentação.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Analytics.Eventos",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/session/{id}": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Eventos de uma sessão",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "400": {
            "description": "sessão inválida",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/analytics/session/{id}.\n\nOnde é usada: tela de sessão — o que aquela visita fez, em ordem.\n\nEfeitos: uma leitura. Sessão de outra conta responde lista VAZIA, e não 404: o id de sessão é opaco e gerado no browser, e distinguir \"não existe\" de \"não é sua\" transformaria a rota num oráculo de existência de sessão alheia.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Analytics.Sessao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/traffic-quality": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Qualidade do tráfego",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_De"
          },
          {
            "$ref": "#/components/parameters/pages_Ate"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/analytics/traffic-quality.\n\nOnde é usada: tela de qualidade — \"esse tráfego pago é gente?\".\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Analytics.Qualidade",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/ab": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Resultado do teste A/B por variante",
        "description": "Sem `experimento`: sessões e conversões por variante na janela (legado). Com `experimento` (ADR-0093): o relatório estatístico de UM teste — por variante, visitas, conversões, taxa com intervalo de Wilson 95%, chance de ser a melhor (Beta-Binomial), peso configurado e efetivo; o estado (coletando / sem_dados_suficientes / inconclusivo / vencedor_provavel) e a série diária. Conversão = sessão com `form_submit`; robôs fora.",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaFiltro"
          },
          {
            "$ref": "#/components/parameters/pages_De"
          },
          {
            "$ref": "#/components/parameters/pages_Ate"
          },
          {
            "name": "experimento",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,64}$"
            }
          },
          {
            "name": "variantes",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "id:peso separados por vírgula (até 10)"
          },
          {
            "name": "desde",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "início do teste"
          },
          {
            "name": "minimo",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "prob",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0.5,
              "maximum": 0.999
            }
          },
          {
            "name": "piso",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 50
            }
          },
          {
            "name": "autobalancear",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "400": {
            "description": "parâmetros do experimento inválidos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Analytics.AB",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/heatmap": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Mapa de calor",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PaginaFiltro"
          },
          {
            "name": "lado",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "resolução da grade"
          },
          {
            "$ref": "#/components/parameters/pages_De"
          },
          {
            "$ref": "#/components/parameters/pages_Ate"
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "400": {
            "description": "lado inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/analytics/heatmap?pagina=&lado=.\n\nOnde é usada: tela de mapa de calor.\n\nEfeitos: uma leitura. # As posições são PORCENTAGEM, e não pixel Pixel absoluto é da tela de quem clicou: o mesmo botão sai em x=320 no celular e x=980 no monitor. A célula em porcentagem compara telas diferentes — que é a única pergunta que um mapa de calor responde.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Analytics.MapaDeCalor",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/live": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Instantâneo em tempo real",
        "responses": {
          "200": {
            "description": "ok"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/analytics/live — sessões e eventos da janela curta.\n\nOnde é usada: cabeçalho da tela ao vivo, no primeiro carregamento.\n\nEfeitos: uma leitura. Existe além do stream porque quem abre a tela precisa ver ALGO antes do primeiro evento chegar. Sem isso, uma conta com movimento baixo mostraria tela vazia por minutos e pareceria quebrada.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.AoVivo.Agora",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/analytics/live/stream": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Stream em tempo real (SSE)",
        "description": "Server-Sent Events do movimento ao vivo.",
        "responses": {
          "200": {
            "description": "fluxo de eventos",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.AoVivo.Stream",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/formularios/{formID}/envios.csv": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "GET /v1/formularios/{formID}/envios.csv?desde=&ate=.",
        "description": "GET /v1/formularios/{formID}/envios.csv?desde=&ate=.\n\nOnde é usada: botão \"exportar\" da tela de leads.\n\nEfeitos: N consultas (a exportação pagina por dentro), uma abertura de AES-GCM por linha, um registro de acesso a PII no log. # Escreve direto no `ResponseWriter`, sem montar o arquivo na memória Um CSV de 10.000 leads com campos livres passa de 10 MB. Montá-lo num buffer para só então enviar duplicaria isso por requisição concorrente, e o pico aconteceria justamente quando várias pessoas exportam no mesmo dia (o dia seguinte a uma campanha). O `encoding/csv` escreve incrementalmente. O custo dessa escolha é honesto e está registrado em `falharNoMeio`: depois do primeiro byte não há mais como trocar o status.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Envios.CSV",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/formularios/{formID}/envios": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "GET /v1/formularios/{formID}/envios?limite=&cursor=&desde=&ate=.",
        "description": "GET /v1/formularios/{formID}/envios?limite=&cursor=&desde=&ate=.\n\nOnde é usada: a tela de leads da ferramenta de Páginas/Formulários.\n\nEfeitos: uma consulta; uma abertura de AES-GCM por linha; um registro de acesso a PII no log. Devolve `{\"envios\": [...], \"proximo_cursor\": \"\"}` — cursor vazio significa que acabou. A alternativa (omitir o campo no fim) obrigaria o cliente a distinguir ausente de vazio, e é o tipo de sutileza que vira laço infinito na tela.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Envios.Listar",
        "x-asender-fonte": "codigo"
      }
    },
    "/v1/formularios/{formID}/analytics": {
      "get": {
        "tags": [
          "Analytics"
        ],
        "summary": "Funil e conversão de um formulário",
        "description": "Visualizações e inícios contam SESSÕES únicas; `envios` vem das submissões gravadas (autoritativo). `taxa_conversao = envios / visualizacoes`; `abandono = (inicios - sessões com envio_ok) / inicios`. Série diária no fuso pedido, com zeros nos dias vazios.",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "parameters": [
          {
            "name": "formID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "de",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "RFC3339 ou AAAA-MM-DD (padrão: 30 dias atrás)"
          },
          {
            "name": "ate",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "RFC3339 ou AAAA-MM-DD (padrão: agora; máx. 400 dias)"
          },
          {
            "name": "fuso",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "America/Sao_Paulo"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "relatório do funil",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "de": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "ate": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "fuso": {
                      "type": "string"
                    },
                    "totais": {
                      "type": "object",
                      "properties": {
                        "impressoes": {
                          "type": "integer"
                        },
                        "visualizacoes": {
                          "type": "integer"
                        },
                        "inicios": {
                          "type": "integer"
                        },
                        "envios": {
                          "type": "integer"
                        },
                        "taxa_conversao": {
                          "type": "number"
                        },
                        "taxa_inicio": {
                          "type": "number"
                        },
                        "abandono": {
                          "type": "number"
                        },
                        "tempo_medio_s": {
                          "type": "number"
                        }
                      }
                    },
                    "serie": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "dia": {
                            "type": "string"
                          },
                          "impressoes": {
                            "type": "integer"
                          },
                          "visualizacoes": {
                            "type": "integer"
                          },
                          "inicios": {
                            "type": "integer"
                          },
                          "envios": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "por_origem": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_RecorteFunil"
                      }
                    },
                    "por_dispositivo": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_RecorteFunil"
                      }
                    },
                    "por_pagina": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_RecorteFunil"
                      }
                    },
                    "abandono_por_campo": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "campo": {
                            "type": "string"
                          },
                          "sessoes": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "parâmetro inválido"
          },
          "401": {
            "description": "sem sessão/conta"
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.AnalyticsDoFormulario.Relatorio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/replay/sessions": {
      "get": {
        "tags": [
          "Session replay"
        ],
        "summary": "Listar as sessões gravadas",
        "parameters": [
          {
            "name": "limite",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sessoes": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "limite inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/replay/sessions?limite=N.\n\nOnde é usada: tela de replay.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Replay.Sessoes",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/replay/sessions/{id}/events": {
      "get": {
        "tags": [
          "Session replay"
        ],
        "summary": "Eventos de uma jornada gravada",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ok"
          },
          "400": {
            "description": "sessão inválida",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/replay/sessions/{id}/events.\n\nOnde é usada: player da tela de replay.\n\nEfeitos: uma leitura. Sessão de outra conta responde lista VAZIA, e não 404: o id é opaco e gerado no browser, e distinguir \"não existe\" de \"não é sua\" transformaria a rota num oráculo de existência de sessão alheia.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Replay.EventosDaSessao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pixels": {
      "get": {
        "tags": [
          "Pixels"
        ],
        "summary": "Listar pixels",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pixels": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Pixel"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/formularios/{formID}/envios?limite=&cursor=&desde=&ate=.\n\nOnde é usada: a tela de leads da ferramenta de Páginas/Formulários.\n\nEfeitos: uma consulta; uma abertura de AES-GCM por linha; um registro de acesso a PII no log. Devolve `{\"envios\": [...], \"proximo_cursor\": \"\"}` — cursor vazio significa que acabou. A alternativa (omitir o campo no fim) obrigaria o cliente a distinguir ausente de vazio, e é o tipo de sutileza que vira laço infinito na tela.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.Listar",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Pixels"
        ],
        "summary": "Criar pixel (devolve o snippet com a credencial)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_PixelUpsert"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Pixel"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "422": {
            "description": "nome inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/pixels.\n\nOnde é usada: tela de pixels.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.Criar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pixels/{id}": {
      "get": {
        "tags": [
          "Pixels"
        ],
        "summary": "Obter um pixel",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Pixel"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "404": {
            "description": "não encontrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/pixels/{id}.\n\nOnde é usada: tela de detalhe do pixel.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.Obter",
        "x-asender-fonte": "spec+codigo"
      },
      "patch": {
        "tags": [
          "Pixels"
        ],
        "summary": "Editar um pixel",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_PixelUpsert"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Pixel"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "404": {
            "description": "não encontrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "422": {
            "description": "informe `nome` e/ou `ativo`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PATCH /v1/pixels/{id}.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma escrita. PATCH parcial de verdade: campo ausente não é tocado. Sem isso, editar só o nome DESATIVARIA o pixel — e a coleta pararia sem ninguém ter pedido.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.Editar",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Pixels"
        ],
        "summary": "Apagar um pixel",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          }
        ],
        "responses": {
          "204": {
            "description": "apagado"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "404": {
            "description": "não encontrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/pixels/{id}.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma escrita; a coleta daquele pixel para.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.Apagar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pixels/{id}/domains": {
      "get": {
        "tags": [
          "Pixels"
        ],
        "summary": "Listar as origens autorizadas do pixel",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dominios": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_DominioDePixel"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/pixels/{id}/domains.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.ListarDominios",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Pixels"
        ],
        "summary": "Autorizar uma origem para o pixel",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_DominioDePixel"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "adicionado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_DominioDePixel"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "409": {
            "description": "host já na lista",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/pixels/{id}/domains.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma escrita — e, a partir dela, o `/collect` ecoa CORS para aquele host.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.AcrescentarDominio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pixels/{id}/domains/{domainId}": {
      "patch": {
        "tags": [
          "Pixels"
        ],
        "summary": "Trocar uma origem autorizada",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          },
          {
            "name": "domainId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_DominioDePixel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_DominioDePixel"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "404": {
            "description": "não encontrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PATCH /v1/pixels/{id}/domains/{domainId}.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.TrocarDominio",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Pixels"
        ],
        "summary": "Remover uma origem autorizada",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          },
          {
            "name": "domainId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "removido"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "404": {
            "description": "não encontrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/pixels/{id}/domains/{domainId}.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma escrita — a origem para de coletar na hora.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.RemoverDominio",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pixels/{id}/conversions": {
      "get": {
        "tags": [
          "Pixels"
        ],
        "summary": "Listar conversões do pixel",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          }
        ],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "conversoes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/pages_Conversao"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "GET /v1/pixels/{id}/conversions.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma leitura.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.ListarConversoes",
        "x-asender-fonte": "spec+codigo"
      },
      "post": {
        "tags": [
          "Pixels"
        ],
        "summary": "Criar conversão",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Conversao"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "criada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Conversao"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "409": {
            "description": "nome já em uso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "422": {
            "description": "nome inválido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "POST /v1/pixels/{id}/conversions.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.CriarConversao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/v1/pixels/{id}/conversions/{cid}": {
      "patch": {
        "tags": [
          "Pixels"
        ],
        "summary": "Editar conversão",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          },
          {
            "name": "cid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_Conversao"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_Conversao"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "404": {
            "description": "não encontrada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          },
          "422": {
            "description": "informe nome, tipo e/ou seletor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "PATCH /v1/pixels/{id}/conversions/{cid}.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.EditarConversao",
        "x-asender-fonte": "spec+codigo"
      },
      "delete": {
        "tags": [
          "Pixels"
        ],
        "summary": "Apagar conversão",
        "parameters": [
          {
            "$ref": "#/components/parameters/pages_PixelID"
          },
          {
            "name": "cid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "apagada"
          },
          "401": {
            "$ref": "#/components/responses/pages_NaoAutorizadoRuntime"
          },
          "404": {
            "description": "não encontrada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/pages_ErroRuntime"
                }
              }
            }
          }
        },
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "description": "DELETE /v1/pixels/{id}/conversions/{cid}.\n\nOnde é usada: tela de detalhe.\n\nEfeitos: uma escrita.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pixels.RemoverConversao",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/chat.js": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "304 quando o `If-None-Match` bate; senão o bundle, com cache de uma hora e `X-Content-Type-Options: nosniff`.",
        "description": "304 quando o `If-None-Match` bate; senão o bundle, com cache de uma hora e `X-Content-Type-Options: nosniff`.\n\nOnde é usada: rota `/chat.js` do runtime.\n\nEntradas: w; r.\n\nEfeitos: nenhum — é leitura de bytes já em memória. **Não há CORS aqui, e é de propósito.** Um `<script src>` não precisa de CORS para executar, e liberar `Access-Control-Allow-Origin: *` neste arquivo só abriria a leitura do conteúdo por `fetch` de qualquer origem sem ganho nenhum. Quem precisa de CORS é a API do chat, e lá a origem é conferida contra a chave de site — não liberada em `*`. `ifNoneMatchCasa` (comparação FRACA, RFC 9110 §13.1.2), não `==`: a borda comprime este script on-the-fly e rebaixa o `ETag` forte para `W/\"…\"` na resposta — o navegador devolve esse `W/` na revisita, e `==` contra o forte nunca batia (achado em `embed.js`, forms-rapidos 2026-10-01; mesma causa aqui, mesmo remédio — ver `etag_match.go`).",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.ChatScript.Servir",
        "x-asender-fonte": "codigo"
      }
    },
    "/_f/{arquivo}": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "304 quando o `If-None-Match` bate; senão o bundle, com cache de uma hora e `X-Content-Type-Options: nosniff`.",
        "description": "304 quando o `If-None-Match` bate; senão o bundle, com cache de uma hora e `X-Content-Type-Options: nosniff`.\n\nOnde é usada: rota `/chat.js` do runtime.\n\nEntradas: w; r.\n\nEfeitos: nenhum — é leitura de bytes já em memória. **Não há CORS aqui, e é de propósito.** Um `<script src>` não precisa de CORS para executar, e liberar `Access-Control-Allow-Origin: *` neste arquivo só abriria a leitura do conteúdo por `fetch` de qualquer origem sem ganho nenhum. Quem precisa de CORS é a API do chat, e lá a origem é conferida contra a chave de site — não liberada em `*`. `ifNoneMatchCasa` (comparação FRACA, RFC 9110 §13.1.2), não `==`: a borda comprime este script on-the-fly e rebaixa o `ETag` forte para `W/\"…\"` na resposta — o navegador devolve esse `W/` na revisita, e `==` contra o forte nunca batia (achado em `embed.js`, forms-rapidos 2026-10-01; mesma causa aqui, mesmo remédio — ver `etag_match.go`).",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Fontes.Servir",
        "x-asender-fonte": "codigo"
      }
    },
    "/asender-sw.js": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "304 quando o `If-None-Match` bate; senão o bundle, com cache de uma hora e `X-Content-Type-Options: nosniff`.",
        "description": "304 quando o `If-None-Match` bate; senão o bundle, com cache de uma hora e `X-Content-Type-Options: nosniff`.\n\nOnde é usada: rota `/chat.js` do runtime.\n\nEntradas: w; r.\n\nEfeitos: nenhum — é leitura de bytes já em memória. **Não há CORS aqui, e é de propósito.** Um `<script src>` não precisa de CORS para executar, e liberar `Access-Control-Allow-Origin: *` neste arquivo só abriria a leitura do conteúdo por `fetch` de qualquer origem sem ganho nenhum. Quem precisa de CORS é a API do chat, e lá a origem é conferida contra a chave de site — não liberada em `*`. `ifNoneMatchCasa` (comparação FRACA, RFC 9110 §13.1.2), não `==`: a borda comprime este script on-the-fly e rebaixa o `ETag` forte para `W/\"…\"` na resposta — o navegador devolve esse `W/` na revisita, e `==` contra o forte nunca batia (achado em `embed.js`, forms-rapidos 2026-10-01; mesma causa aqui, mesmo remédio — ver `etag_match.go`).",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.PushSW.Servir",
        "x-asender-fonte": "codigo"
      }
    },
    "/pixel.js": {
      "get": {
        "tags": [
          "Coleta"
        ],
        "summary": "O script do pixel (anônimo)",
        "description": "Devolve o `pixel.js` a ser carregado pela página publicada. Servido no runtime, com ETag.",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "o script",
            "content": {
              "application/javascript": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "não modificado (ETag)"
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Coleta.Script",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/pixel.{versao:[0-9a-f]{16}}.js": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "GET /pixel.{versao}.js — o mesmo script de `Script`, com `CacheScriptVersionado` quando a versão pedida é a servida e `CacheScriptVersaoAntiga` quando não é.",
        "description": "GET /pixel.{versao}.js — o mesmo script de `Script`, com `CacheScriptVersionado` quando a versão pedida é a servida e `CacheScriptVersaoAntiga` quando não é.\n\nOnde é usada: o `<script src>` de toda página publicada (renderizador, `PixelSrc`).\n\nEfeitos: escreve o script. Versão antiga NÃO vira 404 nem redirect: um 404 desligaria a analytics da página em cache até ela expirar, e um redirect custaria uma ida à rede a mais no caminho de quem só queria o script.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Coleta.ScriptVersionado",
        "x-asender-fonte": "codigo"
      }
    },
    "/collect": {
      "post": {
        "tags": [
          "Coleta"
        ],
        "summary": "Ingerir um lote de eventos de comportamento (anônimo, credencial de pixel)",
        "description": "Chamado pelo `pixel.js` no navegador do visitante. A credencial (token assinado do pixel) vai no corpo. Responde 204; 403 para credencial/origem inválida, 429 para excesso. Servido no runtime.",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_LoteDeColeta"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "aceito"
          },
          "400": {
            "description": "corpo inválido"
          },
          "403": {
            "description": "credencial ou origem inválida"
          },
          "429": {
            "description": "excesso (rate-limit por credencial × IP)"
          },
          "503": {
            "description": "coleta indisponível agora"
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Coleta.Coletar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/replay": {
      "post": {
        "tags": [
          "Session replay"
        ],
        "summary": "Gravar um lote de jornada (anônimo, credencial de pixel)",
        "description": "Chamado pelo pixel no navegador do visitante. A credencial (token assinado do pixel) vai no corpo; o relógio é o do SERVIDOR. Responde 204 quase sempre; 403 para credencial/origem inválida, 429 para excesso. Servido no runtime.",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/pages_LoteDeReplay"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "aceito"
          },
          "400": {
            "description": "corpo inválido"
          },
          "403": {
            "description": "credencial ou origem inválida"
          },
          "429": {
            "description": "excesso (rate-limit por credencial × IP)"
          },
          "503": {
            "description": "gravação indisponível agora"
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Replay.Gravar",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/r/{slug}": {
      "get": {
        "tags": [
          "Coleta"
        ],
        "summary": "Resolver um link curto (anônimo)",
        "description": "Redireciona ao destino do link e conta o clique. Servido no runtime.",
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "redireciona ao destino"
          },
          "404": {
            "description": "link inexistente ou inativo"
          }
        },
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pagina.Servir",
        "x-asender-fonte": "spec+codigo"
      }
    },
    "/robots.txt": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "libera a indexação e aponta o sitemap do MESMO host — ou fecha o host inteiro (`Disallow: /`) quando ele não deve ir à busca (ver `fechado`).",
        "description": "libera a indexação e aponta o sitemap do MESMO host — ou fecha o host inteiro (`Disallow: /`) quando ele não deve ir à busca (ver `fechado`).\n\nOnde é usada: rota pública.\n\nEfeitos: uma leitura no `asender_pages_api` (quando há fonte). # Por que liberar é o padrão A página existe para ser achada: é landing page de campanha, e quem a publica quer tráfego. Fechar o host é DECISÃO — do cliente (noindex do site), do estado (nada publicado e indexável) ou da política (`PAGES_INDEXACAO`) —, nunca o default da plataforma. Falha ao consultar o `asender_pages_api` LIBERA: um blip não pode tirar o site de um cliente da busca (o cache curto de 5 min do documento fechado vale pelo mesmo motivo). O noindex de UMA página não vira `Disallow`: o buscador precisa conseguir ler a página para ver o `noindex` dela — bloqueada no robots, ela pode continuar no índice pelos links que apontam para ela. O sitemap é apontado com o HOST da requisição, e não com um domínio configurado: cada cliente tem o dele, e um endereço fixo aqui mandaria o buscador de todo mundo para o sitemap de um só.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.SEO.Robots",
        "x-asender-fonte": "codigo"
      }
    },
    "/sitemap.xml": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "lista as páginas PUBLICADAS e INDEXÁVEIS daquele host (sem noindex, sem canônica externa; a home do domínio pela raiz). Host fechado pela política ou site com noindex global → documento vazio.",
        "description": "lista as páginas PUBLICADAS e INDEXÁVEIS daquele host (sem noindex, sem canônica externa; a home do domínio pela raiz). Host fechado pela política ou site com noindex global → documento vazio.\n\nOnde é usada: rota pública.\n\nEfeitos: uma leitura no `asender_pages_api`. # Host sem página responde XML VAZIO, e não 404 Um 404 no sitemap faz o buscador registrar erro e tentar de novo; um documento vazio diz \"não há nada para indexar aqui\", que é a verdade. E o host que ainda não tem página publicada é o caso mais comum logo depois de alguém cadastrar um domínio. # Rascunho não entra A consulta do `asender_pages_api` filtra por versão publicada. Listar rascunho entregaria ao buscador o endereço de uma página que o cliente ainda não quis mostrar — e o buscador não esquece.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.SEO.Sitemap",
        "x-asender-fonte": "codigo"
      }
    },
    "/favicon.ico": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "redireciona (302) para o favicon do site (o de SEO, senão o da marca) quando o dono do host configurou um https; senão para o padrão `/favicon.svg`.",
        "description": "redireciona (302) para o favicon do site (o de SEO, senão o da marca) quando o dono do host configurou um https; senão para o padrão `/favicon.svg`.\n\nOnde é usada: rota pública, pedida pelo navegador e pelo buscador por convenção.\n\nEfeitos: uma leitura no `asender_pages_api` (quando há fonte). 302 e não 301: o favicon do site muda quando o cliente troca a marca, e um 301 ficaria gravado no navegador do visitante para sempre. Falha na consulta cai no padrão — favicon nunca vale um erro.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.SEO.Favicon",
        "x-asender-fonte": "codigo"
      }
    },
    "/favicon.svg": {
      "get": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "o SVG fixo, cacheável por um dia.",
        "description": "o SVG fixo, cacheável por um dia.\n\nOnde é usada: rota pública; é o `<link rel=\"icon\">` da página sem favicon (seo.go do renderizador).\n\nEfeitos: nenhum.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.SEO.FaviconPadrao",
        "x-asender-fonte": "codigo"
      }
    },
    "/{slug}": {
      "post": {
        "servers": [
          {
            "url": "https://api.pages.asender.net",
            "description": "Páginas"
          }
        ],
        "tags": [
          "pages_runtime"
        ],
        "summary": "resolve a página pela mesma rota que a serviu, monta a submissão com `Modo = pagina` e chama o MESMO pipeline dos outros dois modos.",
        "description": "resolve a página pela mesma rota que a serviu, monta a submissão com `Modo = pagina` e chama o MESMO pipeline dos outros dois modos.\n\nOnde é usada: POST no caminho da própria página.\n\nEfeitos: os do pipeline (banco, outbox). O tenant e o form vêm da PÁGINA resolvida no servidor, nunca do corpo: o form da página é HTML público, e qualquer um pode reenviá-lo com outro `form_id`. Resolver pelo host+caminho é o que impede postar no formulário de outro tenant.",
        "x-asender-servico": "asender_pages_runtime",
        "x-asender-handler": "d.Pagina.Submeter",
        "x-asender-fonte": "codigo"
      }
    }
  },
  "components": {
    "schemas": {
      "platform_ProtectedResourceMetadata": {
        "type": "object",
        "description": "Documento RFC 9728.",
        "properties": {
          "resource": {
            "type": "string",
            "examples": [
              "https://api.asender.net/mcp"
            ]
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "https://auth.asender.net"
              ]
            ]
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "bearer_methods_supported": {
            "type": "array",
            "items": {
              "type": "string",
              "const": "header"
            }
          },
          "resource_documentation": {
            "type": "string",
            "examples": [
              "https://developers.asender.net/guias/mcp.html"
            ]
          },
          "resource_name": {
            "type": "string",
            "const": "Asender"
          }
        }
      },
      "platform_DataError": {
        "type": "object",
        "required": [
          "error"
        ],
        "description": "Envelope de erro das rotas de dados (onda 2): `{\"error\":{\"type\",\"message\"}}`. As rotas legadas de envio mantêm o envelope `ErrorEnvelope`.",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "message"
            ],
            "properties": {
              "type": {
                "type": "string",
                "examples": [
                  "InvalidParameter"
                ],
                "description": "Código estável: `InvalidParameter`, `NotFound`, `Forbidden`, `InsufficientScope`, `AuthorizationError`, `UpstreamError`, `ServiceUnavailable`."
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "platform_DataObject": {
        "type": "object",
        "description": "Documento agregado `{\"data\": {...}}` (relatórios). Chaves em inglês `snake_case`; contadores inteiros, taxas entre 0 e 1.",
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "platform_Campaign": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "template": {
            "type": [
              "string",
              "null"
            ]
          },
          "list_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "segment_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "recipients": {
            "type": "integer"
          },
          "sent": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "pause_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "recurrence_cron": {
            "type": [
              "string",
              "null"
            ]
          },
          "recurrence_tz": {
            "type": [
              "string",
              "null"
            ]
          },
          "recurrence_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "platform_CampaignList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_Campaign"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_MessagingMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "queued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "segments": {
            "type": "integer",
            "description": "Só SMS."
          },
          "title": {
            "type": "string",
            "description": "Só push."
          }
        }
      },
      "platform_MessagingMessageList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_MessagingMessage"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_DataCounts": {
        "type": "object",
        "properties": {
          "visits": {
            "type": "integer"
          },
          "pageviews": {
            "type": "integer"
          },
          "conversions": {
            "type": "integer"
          },
          "conversion_rate": {
            "type": "number",
            "description": "conversões ÷ visitas (4 casas)."
          },
          "form_views": {
            "type": "integer"
          },
          "form_starts": {
            "type": "integer"
          },
          "form_submissions": {
            "type": "integer"
          }
        }
      },
      "platform_DataAnalyticsOverview": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "totals": {
            "$ref": "#/components/schemas/platform_DataCounts"
          }
        }
      },
      "platform_DataAnalyticsSeries": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "interval": {
            "type": "string",
            "enum": [
              "day"
            ]
          },
          "timezone": {
            "type": "string",
            "examples": [
              "America/Sao_Paulo"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/platform_DataCounts"
                },
                {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    }
                  }
                }
              ]
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_DataAnalyticsBreakdown": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "dimension": {
            "type": "string"
          },
          "data": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/platform_DataCounts"
                },
                {
                  "type": "object",
                  "properties": {
                    "value": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_DataAnalyticsForms": {
        "description": "Sem `form_id`: `data` (resumo por formulário). Com `form_id`: o funil (`totals`, `series`, `by_source`, `by_device`, `by_page`, `field_abandonment`).",
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "form_id": {
            "type": "string"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "form_id": {
                  "type": "string"
                },
                "views": {
                  "type": "integer"
                },
                "starts": {
                  "type": "integer"
                },
                "submissions": {
                  "type": "integer"
                },
                "conversion_rate": {
                  "type": "number"
                }
              }
            }
          },
          "totals": {
            "type": "object",
            "additionalProperties": true
          },
          "series": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "by_source": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "by_device": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "by_page": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "field_abandonment": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_DataConversation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "cnv_0123456789abcdef"
            ]
          },
          "channel": {
            "type": "string",
            "examples": [
              "chat"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "aberto",
              "pendente",
              "resolvido",
              "fechado"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "baixa",
              "normal",
              "alta",
              "urgente"
            ]
          },
          "subject": {
            "type": "string"
          },
          "lead_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id público do lead: o MESMO de `GET /v1/leads` (`id`)."
          },
          "assigned_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "queue_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Última atividade (inclui mensagem nova)."
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "message_count": {
            "type": "integer"
          }
        }
      },
      "platform_DataConversationList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_DataConversation"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_DataMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "msg_0123456789abcdef"
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "author_type": {
            "type": "string",
            "enum": [
              "contact",
              "agent",
              "bot",
              "device",
              "api"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "text"
            ]
          },
          "text": {
            "type": "string"
          },
          "delivery_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          },
          "attachment_count": {
            "type": "integer"
          }
        }
      },
      "platform_DataMessageList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_DataMessage"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_DataJourney": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "kind": {
            "type": "string",
            "enum": [
              "marketing",
              "bot"
            ]
          },
          "trigger": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "args": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "number": {
                  "type": "integer"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "esperar",
                    "agir",
                    "condicao"
                  ]
                },
                "action": {
                  "type": "string"
                },
                "wait_seconds": {
                  "type": "integer"
                }
              }
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "entries": {
                "type": "integer"
              },
              "in_progress": {
                "type": "integer"
              },
              "exits": {
                "type": "integer"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_DataJourneyList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_DataJourney"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_LimitePlano": {
        "type": "object",
        "description": "Teto de um plano num domínio de quota. `null` = sem limite na escala.",
        "required": [
          "dia",
          "mes"
        ],
        "properties": {
          "dia": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "mes": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          }
        }
      },
      "platform_PlanoCatalogo": {
        "type": "object",
        "description": "Um plano publicado do catálogo único (ADR-0075).",
        "required": [
          "slug",
          "nome",
          "descricao",
          "preco_centavos",
          "periodo",
          "destaque",
          "cta",
          "ordem",
          "limites"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "examples": [
              "basico",
              "pro",
              "business"
            ]
          },
          "nome": {
            "type": "string"
          },
          "descricao": {
            "type": "string"
          },
          "preco_centavos": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "minimum": 0,
            "description": "Preço em centavos da `moeda` do topo. `null` = sob consulta."
          },
          "periodo": {
            "type": "string",
            "enum": [
              "mes",
              "ano"
            ]
          },
          "destaque": {
            "type": "boolean"
          },
          "cta": {
            "type": "string",
            "enum": [
              "contratar",
              "contato"
            ]
          },
          "ordem": {
            "type": "integer"
          },
          "limites": {
            "type": "object",
            "description": "Domínio de quota (email_corporativo, email_marketing, sms…) → teto.",
            "additionalProperties": {
              "$ref": "#/components/schemas/platform_LimitePlano"
            }
          }
        }
      },
      "platform_CatalogoPlanos": {
        "type": "object",
        "required": [
          "moeda",
          "planos"
        ],
        "properties": {
          "moeda": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "examples": [
              "BRL"
            ]
          },
          "planos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_PlanoCatalogo"
            }
          }
        }
      },
      "platform_LogsErrorEnvelope": {
        "type": "object",
        "description": "Envelope de erro do serviço de logs (repassado sem reescrita; chaves em minúsculas).",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "examples": [
                  "ValidationError"
                ]
              },
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "platform_LogResumo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "log_3f9a1c0b2d4e5f60"
            ]
          },
          "object": {
            "type": "string",
            "enum": [
              "log"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string",
            "description": "Caminho como chamado (sem a query)."
          },
          "route": {
            "type": "string",
            "description": "Padrão de rota (ex.: `/v1/emails/{id}`)."
          },
          "status": {
            "type": "integer"
          },
          "duration_ms": {
            "type": "integer"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "api_key": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "idempotency_key": {
            "type": "string"
          },
          "error_code": {
            "type": "string",
            "description": "Código do envelope de erro; vazio em sucesso."
          },
          "message_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ids de mensagem devolvidos pelo envio."
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "platform_LogDetalhe": {
        "allOf": [
          {
            "$ref": "#/components/schemas/platform_LogResumo"
          },
          {
            "type": "object",
            "properties": {
              "service": {
                "type": "string"
              },
              "query": {
                "type": "string",
                "description": "Query string, já redigida."
              },
              "trace_id": {
                "type": "string"
              },
              "request_body": {
                "description": "Corpo da requisição redigido (JSON ou texto); `null` se não havia.",
                "nullable": true
              },
              "request_body_truncated": {
                "type": "boolean"
              },
              "response_body": {
                "description": "Corpo da resposta redigido; `null` se não havia.",
                "nullable": true
              },
              "response_body_truncated": {
                "type": "boolean"
              },
              "request_size": {
                "type": "integer"
              },
              "response_size": {
                "type": "integer"
              },
              "redaction_version": {
                "type": "integer"
              }
            }
          }
        ]
      },
      "platform_DataOrigem": {
        "type": "object",
        "description": "Atribuição do lead ou da conversão.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Fonte declarada do lead (ex.: google, indicação)."
          },
          "utm_source": {
            "type": "string"
          },
          "utm_medium": {
            "type": "string"
          },
          "utm_campaign": {
            "type": "string"
          },
          "utm_term": {
            "type": "string"
          },
          "utm_content": {
            "type": "string"
          },
          "referrer": {
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "form_id": {
            "type": "string"
          }
        }
      },
      "platform_Lead": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id público do lead: o mesmo `lead_id` de conversões e conversas."
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "job_title": {
            "type": "string"
          },
          "stage": {
            "type": "string",
            "description": "Etapa do funil no CRM (valor definido pela conta)."
          },
          "lifecycle": {
            "type": "string",
            "enum": [
              "lead",
              "contact"
            ]
          },
          "source": {
            "$ref": "#/components/schemas/platform_DataOrigem"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "custom_fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Campos personalizados da conta: as chaves são da conta e não são traduzidas."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_LeadCreate": {
        "type": "object",
        "additionalProperties": false,
        "description": "Corpo fechado de `POST /v1/leads`: ao menos `email` ou `phone`. Não existe `id`, `tenant_id`, dono, empresa, consentimento nem origem/UTM.",
        "anyOf": [
          {
            "required": [
              "email"
            ]
          },
          {
            "required": [
              "phone"
            ]
          }
        ],
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 320,
            "description": "Identificador do lead; guardado cifrado. Não pode ser vazio quando enviado."
          },
          "phone": {
            "type": "string",
            "maxLength": 32,
            "description": "Formato internacional, 8 a 15 dígitos (ex.: `+55 11 91234-5678`); guardado cifrado."
          },
          "first_name": {
            "type": "string",
            "maxLength": 120
          },
          "last_name": {
            "type": "string",
            "maxLength": 256,
            "description": "`\"\"` limpa (PATCH)."
          },
          "job_title": {
            "type": "string",
            "maxLength": 256
          },
          "department": {
            "type": "string",
            "maxLength": 256
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "ISO 3166-1 alpha-2 em maiúsculas."
          },
          "state": {
            "type": "string",
            "maxLength": 256
          },
          "city": {
            "type": "string",
            "maxLength": 256
          },
          "language": {
            "type": "string",
            "maxLength": 256
          },
          "linkedin": {
            "type": "string",
            "maxLength": 256
          },
          "stage": {
            "type": "string",
            "enum": [
              "novo",
              "contatado",
              "qualificado",
              "cliente",
              "descartado"
            ],
            "description": "Etapa do funil (os mesmos valores que a leitura devolve). Padrão na criação: `novo`."
          },
          "tags": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "description": "No PATCH a lista SUBSTITUI a atual (vazia limpa)."
          },
          "custom_fields": {
            "type": "object",
            "maxProperties": 60,
            "additionalProperties": {
              "type": "string",
              "maxLength": 2000
            },
            "description": "Chaves de até 80 caracteres. No PATCH mescla; valor `\"\"` remove a chave."
          }
        }
      },
      "platform_LeadUpdate": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "description": "Corpo fechado de `PATCH /v1/leads/{id}`: ao menos um campo; o que não vem é preservado.",
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 320,
            "description": "Identificador do lead; guardado cifrado. Não pode ser vazio quando enviado."
          },
          "phone": {
            "type": "string",
            "maxLength": 32,
            "description": "Formato internacional, 8 a 15 dígitos (ex.: `+55 11 91234-5678`); guardado cifrado."
          },
          "first_name": {
            "type": "string",
            "maxLength": 120
          },
          "last_name": {
            "type": "string",
            "maxLength": 256,
            "description": "`\"\"` limpa (PATCH)."
          },
          "job_title": {
            "type": "string",
            "maxLength": 256
          },
          "department": {
            "type": "string",
            "maxLength": 256
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "ISO 3166-1 alpha-2 em maiúsculas."
          },
          "state": {
            "type": "string",
            "maxLength": 256
          },
          "city": {
            "type": "string",
            "maxLength": 256
          },
          "language": {
            "type": "string",
            "maxLength": 256
          },
          "linkedin": {
            "type": "string",
            "maxLength": 256
          },
          "stage": {
            "type": "string",
            "enum": [
              "novo",
              "contatado",
              "qualificado",
              "cliente",
              "descartado"
            ],
            "description": "Etapa do funil (os mesmos valores que a leitura devolve). Padrão na criação: `novo`."
          },
          "tags": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "description": "No PATCH a lista SUBSTITUI a atual (vazia limpa)."
          },
          "custom_fields": {
            "type": "object",
            "maxProperties": 60,
            "additionalProperties": {
              "type": "string",
              "maxLength": 2000
            },
            "description": "Chaves de até 80 caracteres. No PATCH mescla; valor `\"\"` remove a chave."
          }
        }
      },
      "platform_LeadList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_Lead"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_FormField": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "required": {
            "type": "boolean"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "key": {
            "type": "string"
          }
        }
      },
      "platform_Form": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "external_id": {
            "type": "string"
          },
          "requires_consent": {
            "type": "boolean"
          },
          "legal_basis": {
            "type": "string"
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_FormField"
            }
          }
        }
      },
      "platform_FormList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_Form"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_FormSubmission": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "form_id": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "page",
              "snippet",
              "api"
            ]
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Valores digitados, em claro; as chaves são as do formulário e não são traduzidas."
          },
          "undecipherable": {
            "type": "boolean"
          },
          "source": {
            "type": "object",
            "properties": {
              "utm_source": {
                "type": "string"
              },
              "utm_medium": {
                "type": "string"
              },
              "utm_campaign": {
                "type": "string"
              },
              "referrer": {
                "type": "string"
              }
            }
          },
          "consent": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string"
              },
              "legal_basis": {
                "type": "string"
              },
              "at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Quando o envio foi recebido; base de `created_after`."
          }
        }
      },
      "platform_FormSubmissionList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_FormSubmission"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_Conversion": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "lead_id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "sent",
              "ignored",
              "result"
            ]
          },
          "event": {
            "type": "string"
          },
          "event_ref": {
            "type": "string"
          },
          "value": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "platform": {
            "type": "string",
            "description": "meta_capi, google_ads ou tiktok_events."
          },
          "delivery_status": {
            "type": "string",
            "description": "O desfecho da entrega à plataforma (ex.: enviado, simulado, falhou, morta, ignorada_sem_consentimento)."
          },
          "reason": {
            "type": "string"
          },
          "source": {
            "$ref": "#/components/schemas/platform_DataOrigem"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Quando o fato foi registrado; base de `created_after`."
          }
        }
      },
      "platform_ConversionList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_Conversion"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "platform_LogsList": {
        "type": "object",
        "required": [
          "object",
          "has_more",
          "next",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "has_more": {
            "type": "boolean"
          },
          "next": {
            "type": "string",
            "description": "Cursor para `after`; vazio quando `has_more` é `false`."
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_LogResumo"
            }
          }
        }
      },
      "platform_LogsSerie": {
        "type": "object",
        "properties": {
          "bucket": {
            "type": "string"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "t": {
                  "type": "string",
                  "format": "date-time"
                },
                "2xx": {
                  "type": "integer"
                },
                "3xx": {
                  "type": "integer"
                },
                "4xx": {
                  "type": "integer"
                },
                "5xx": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "platform_LogsResumo": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer"
          },
          "errors_4xx": {
            "type": "integer"
          },
          "errors_5xx": {
            "type": "integer"
          },
          "error_rate": {
            "type": "number",
            "description": "(4xx+5xx)/total, de 0 a 1."
          },
          "p50_ms": {
            "type": "number"
          },
          "p95_ms": {
            "type": "number"
          },
          "by_route": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "method": {
                  "type": "string"
                },
                "route": {
                  "type": "string"
                },
                "total": {
                  "type": "integer"
                },
                "errors": {
                  "type": "integer"
                },
                "error_rate": {
                  "type": "number"
                },
                "p95_ms": {
                  "type": "number"
                }
              }
            }
          },
          "by_error_code": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "platform_ErrorEnvelope": {
        "type": "object",
        "required": [
          "Error",
          "RequestId"
        ],
        "properties": {
          "Error": {
            "type": "object",
            "required": [
              "Type",
              "Code",
              "Message"
            ],
            "properties": {
              "Type": {
                "type": "string",
                "enum": [
                  "Sender",
                  "Receiver"
                ],
                "description": "Derivado do status: 4xx→Sender, 5xx→Receiver. O SDK decide retry por aqui."
              },
              "Code": {
                "type": "string",
                "examples": [
                  "ValidationError",
                  "AuthorizationError",
                  "NotFound",
                  "TenantSuspended",
                  "NoTenant",
                  "Conflict",
                  "PayloadTooLarge",
                  "Throttling",
                  "InternalError"
                ]
              },
              "Message": {
                "type": "string"
              },
              "Details": {
                "description": "Presente só na validação SES do `/v1/*`: mapa campo → motivo. Omitido quando vazio.",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "platform_FrotaPontos": {
        "type": "array",
        "description": "Serie de ate 72 pontos (passo 300 s); buracos ficam ausentes, sem interpolacao. Cpu/Ram/Disco em percentual 0-100; carga em razao por nucleo.",
        "items": {
          "type": "object",
          "properties": {
            "T": {
              "type": "integer",
              "format": "int64",
              "description": "Segundos Unix UTC."
            },
            "V": {
              "type": "number"
            }
          }
        }
      },
      "platform_RequestIdEnvelope": {
        "type": "object",
        "required": [
          "RequestId"
        ],
        "properties": {
          "RequestId": {
            "type": "string",
            "description": "Presente em TODA resposta de sucesso. Espelha o header\n`X-Request-Id`. **Divergência:** um `X-Request-Id` mandado pelo\ncliente é ecoado cru e vira o correlation id do log\n(RELATORIO-FINAL §3 item 15)."
          }
        }
      },
      "platform_CursorPage": {
        "type": "object",
        "properties": {
          "NextCursor": {
            "type": "integer",
            "description": "Cursor da próxima página; 0 quando acabou."
          },
          "HasMore": {
            "type": "boolean"
          }
        }
      },
      "platform_Channel": {
        "type": "string",
        "enum": [
          "email",
          "sms",
          "push"
        ]
      },
      "platform_MessageStatus": {
        "type": "string",
        "enum": [
          "queued",
          "sending",
          "sent",
          "delivered",
          "bounced",
          "failed",
          "opened",
          "clicked",
          "suppressed"
        ],
        "description": "`suppressed` — o Asender decidiu NÃO enviar (supressão global; ver\n`SuppressionReason`). Não é falha nem bounce e não entra nessas taxas."
      },
      "platform_SuppressionReason": {
        "type": "string",
        "description": "Por que o envio foi suprimido (ADR-0134 D4). `Message`/`ErrorMessage` trazem o texto humano correspondente.",
        "enum": [
          "tld_inexistente",
          "dominio_de_teste",
          "dominio_inexistente",
          "dominio_sem_correio",
          "caixa_inexistente",
          "recusas_repetidas",
          "manual",
          "supressao_global",
          "descadastro"
        ],
        "x-enum-descriptions": {
          "tld_inexistente": "O TLD do domínio não existe na lista da IANA.",
          "dominio_de_teste": "Domínio reservado para teste (.test, .invalid, .example, example.*…).",
          "dominio_inexistente": "O domínio não existe (sem MX nem A).",
          "dominio_sem_correio": "O domínio declara que não recebe e-mail (null MX).",
          "caixa_inexistente": "A caixa não existe (recusada pelo provedor).",
          "recusas_repetidas": "O endereço recusou 2 envios em 30 dias.",
          "manual": "Endereço na supressão global por entrada manual.",
          "supressao_global": "Destinatário na supressão global (origem desconhecida/legado).",
          "descadastro": "O destinatário se descadastrou de e-mails desta conta."
        }
      },
      "platform_SuppressedRecipient": {
        "type": "object",
        "required": [
          "Email",
          "Reason",
          "Message"
        ],
        "properties": {
          "Email": {
            "type": "string"
          },
          "Reason": {
            "$ref": "#/components/schemas/platform_SuppressionReason"
          },
          "Message": {
            "type": "string",
            "description": "Texto humano em português; pode ser exibido ao usuário final."
          },
          "MessageId": {
            "type": "string",
            "description": "Id da linha `suppressed` persistida (consultável em `GET /v1/emails/{id}` e na tela Envios do painel). Ausente se a persistência estava indisponível."
          }
        }
      },
      "platform_ErrorEnvelopeSuppressed": {
        "type": "object",
        "required": [
          "Error",
          "RequestId"
        ],
        "description": "Como `ErrorEnvelope`, mas `Details` pode trazer `Suppressed[]` (código `SuppressedRecipient`).",
        "properties": {
          "Error": {
            "type": "object",
            "required": [
              "Type",
              "Code",
              "Message"
            ],
            "properties": {
              "Type": {
                "type": "string",
                "enum": [
                  "Sender",
                  "Receiver"
                ]
              },
              "Code": {
                "type": "string",
                "examples": [
                  "SuppressedRecipient",
                  "RemetenteNaoVerificado",
                  "ValidationError"
                ]
              },
              "Message": {
                "type": "string"
              },
              "Details": {
                "type": "object",
                "properties": {
                  "Suppressed": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/platform_SuppressedRecipient"
                    }
                  }
                },
                "additionalProperties": true
              }
            }
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "platform_Message": {
        "type": "object",
        "description": "Projeção de listagem: SEM corpo (payload grande em lista é desperdício).",
        "properties": {
          "Id": {
            "type": "string"
          },
          "TenantId": {
            "type": "string"
          },
          "Channel": {
            "$ref": "#/components/schemas/platform_Channel"
          },
          "Status": {
            "$ref": "#/components/schemas/platform_MessageStatus"
          },
          "From": {
            "type": "string"
          },
          "To": {
            "type": "string"
          },
          "Subject": {
            "type": "string"
          },
          "Provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "ProviderMessageId": {
            "type": [
              "string",
              "null"
            ]
          },
          "ErrorCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "ErrorMessage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Texto humano (em suprimidos, o texto do motivo — nunca JSON cru)."
          },
          "SuppressionReason": {
            "description": "Só em `Status: suppressed`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/platform_SuppressionReason"
              },
              {
                "type": "null"
              }
            ]
          },
          "QueuedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "SentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "DeliveredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "FailedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "OpenedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ClickedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_MessagePage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/platform_RequestIdEnvelope"
          },
          {
            "$ref": "#/components/schemas/platform_CursorPage"
          },
          {
            "type": "object",
            "properties": {
              "Messages": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/platform_Message"
                }
              }
            }
          }
        ]
      },
      "platform_MessageDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/platform_RequestIdEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "Message": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/platform_Message"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "BodyText": {
                        "type": "string"
                      },
                      "BodyHtml": {
                        "type": "string"
                      },
                      "Metadata": {
                        "type": "object",
                        "additionalProperties": true,
                        "description": "VALOR OPACO — chaves internas passam intactas (não são pascalizadas)."
                      }
                    }
                  }
                ]
              },
              "Events": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "Type": {
                      "type": "string"
                    },
                    "OccurredAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "Payload": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "Valor opaco."
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "platform_SendEmailRequest": {
        "type": "object",
        "required": [
          "Source",
          "Destination",
          "Message"
        ],
        "properties": {
          "Source": {
            "type": "string",
            "format": "email"
          },
          "Destination": {
            "type": "object",
            "properties": {
              "ToAddresses": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "email"
                }
              },
              "CcAddresses": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "email"
                }
              },
              "BccAddresses": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "email"
                }
              }
            },
            "description": "Ao menos um endereço somando To+Cc+Bcc."
          },
          "Message": {
            "type": "object",
            "required": [
              "Subject",
              "Body"
            ],
            "properties": {
              "Subject": {
                "type": "object",
                "properties": {
                  "Data": {
                    "type": "string",
                    "minLength": 1
                  },
                  "Charset": {
                    "type": "string"
                  }
                }
              },
              "Body": {
                "type": "object",
                "description": "Ao menos um de Text.Data ou Html.Data.",
                "properties": {
                  "Text": {
                    "type": "object",
                    "properties": {
                      "Data": {
                        "type": "string"
                      },
                      "Charset": {
                        "type": "string"
                      }
                    }
                  },
                  "Html": {
                    "type": "object",
                    "properties": {
                      "Data": {
                        "type": "string"
                      },
                      "Charset": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "ReplyToAddresses": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "ReturnPath": {
            "type": "string",
            "format": "email"
          },
          "IdempotencyKey": {
            "type": "string",
            "description": "É ESTE campo que funciona, não o header."
          },
          "Tags": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "platform_BatchResult": {
        "type": "object",
        "properties": {
          "Index": {
            "type": "integer"
          },
          "Status": {
            "type": "string",
            "enum": [
              "queued",
              "accepted",
              "rejected",
              "failed"
            ],
            "description": "`accepted`: serviço de mensageria indisponível; item no spool durável, reenviado com `IdempotencyKey` (`<chave>:<índice>` ou gerada)."
          },
          "MessageId": {
            "type": "string"
          },
          "MessageIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "IdempotencyKey": {
            "type": "string",
            "description": "Só com `Status: accepted`."
          },
          "ReusedIdempotency": {
            "type": "boolean"
          },
          "Error": {
            "type": "object",
            "properties": {
              "Code": {
                "type": "string",
                "description": "Ex.: `ValidationError`, `RemetenteNaoVerificado`, `IdempotencyConflict`, `InternalError`."
              },
              "Message": {
                "type": "string"
              },
              "Details": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "platform_Template": {
        "type": "object",
        "properties": {
          "Id": {
            "type": "string"
          },
          "TenantId": {
            "type": "string"
          },
          "Slug": {
            "type": "string"
          },
          "Name": {
            "type": "string"
          },
          "Channel": {
            "$ref": "#/components/schemas/platform_Channel"
          },
          "Subject": {
            "type": "string"
          },
          "BodyHtml": {
            "type": "string"
          },
          "BodyText": {
            "type": "string"
          },
          "Variables": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Valor opaco — não pascalizado."
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_ContactInput": {
        "type": "object",
        "additionalProperties": false,
        "description": "Ao menos um de Email ou Phone.",
        "properties": {
          "Email": {
            "type": "string"
          },
          "Phone": {
            "type": "string"
          },
          "Name": {
            "type": "string"
          },
          "Attributes": {
            "type": "object",
            "additionalProperties": true,
            "description": "Valor opaco."
          },
          "Subscribed": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "platform_Contact": {
        "type": "object",
        "properties": {
          "Id": {
            "type": "string"
          },
          "TenantId": {
            "type": "string"
          },
          "Email": {
            "type": "string"
          },
          "Phone": {
            "type": "string"
          },
          "Name": {
            "type": "string"
          },
          "Attributes": {
            "type": "object",
            "additionalProperties": true
          },
          "Tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Sempre array (vazio sem tags), nunca null."
          },
          "Subscribed": {
            "type": "boolean"
          },
          "UnsubscribedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_ContactTagsBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ContactIds",
          "Tags"
        ],
        "properties": {
          "ContactIds": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "type": "string"
            }
          },
          "Tags": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "type": "string"
            }
          }
        }
      },
      "platform_ContactList": {
        "type": "object",
        "properties": {
          "Id": {
            "type": "string"
          },
          "TenantId": {
            "type": "string"
          },
          "Name": {
            "type": "string"
          },
          "Slug": {
            "type": "string"
          },
          "Description": {
            "type": "string"
          },
          "MemberCount": {
            "type": "integer"
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_CampaignBody": {
        "type": "object",
        "description": "Payload PascalCase de POST/PATCH `/api/campaigns`. O casing público\nespelha o do front 1:1; a tradução para snake_case do asender_mkt_api é\ncampo a campo (toInput), para que nada inesperado do cliente chegue ao\nupstream (mass assignment, §37 item 14).\n\n**O público é UM dos dois:** `ListId` (associação estática) ou\n`SegmentoId` (consulta viva, resolvida no disparo). O asender_mkt_api\nrecusa os dois preenchidos pelo CHECK `campaigns_um_publico_apenas`, e o\nBFF omite do corpo o que não foi escolhido.",
        "required": [
          "Name",
          "Channel"
        ],
        "properties": {
          "Name": {
            "type": "string"
          },
          "Channel": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "push",
              "whatsapp"
            ]
          },
          "TemplateId": {
            "type": "string"
          },
          "ListId": {
            "type": "string",
            "description": "id público da lista (`lst_…`); exclusivo com SegmentoId."
          },
          "SegmentoId": {
            "type": "string",
            "description": "id público do segmento (`seg_…`); exclusivo com ListId."
          },
          "Subject": {
            "type": "string"
          },
          "FromAddress": {
            "type": "string"
          }
        }
      },
      "platform_DnsZone": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "examples": [
              "acme.com"
            ]
          },
          "connection_id": {
            "type": "string",
            "examples": [
              "dnc_0123456789abcdef"
            ]
          }
        }
      },
      "platform_DnsZones": {
        "type": "object",
        "required": [
          "zones"
        ],
        "properties": {
          "zones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_DnsZone"
            }
          },
          "warnings": {
            "type": "array",
            "description": "Conexões cuja consulta à Cloudflare falhou (as zonas delas vêm do cache).",
            "items": {
              "type": "object",
              "properties": {
                "connection_id": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "examples": [
                    "ProvedorIndisponivel"
                  ]
                }
              }
            }
          }
        }
      },
      "platform_DnsRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "examples": [
              "TXT"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "_dmarc.acme.com"
            ]
          },
          "content": {
            "type": "string"
          },
          "ttl": {
            "type": "integer",
            "description": "1 = automático."
          },
          "priority": {
            "type": "integer",
            "description": "Só no MX."
          },
          "proxied": {
            "type": "boolean"
          },
          "comment": {
            "type": "string",
            "maxLength": 60,
            "description": "Criado pela Asender: `asender:<ator>`."
          }
        }
      },
      "platform_DnsRecords": {
        "type": "object",
        "required": [
          "records"
        ],
        "properties": {
          "records": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_DnsRecord"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          }
        }
      },
      "platform_DnsRecordCreate": {
        "type": "object",
        "required": [
          "type",
          "name",
          "content"
        ],
        "additionalProperties": false,
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "A",
              "AAAA",
              "CNAME",
              "TXT",
              "MX",
              "NS"
            ]
          },
          "name": {
            "type": "string",
            "description": "'@', nome relativo à zona ou FQDN."
          },
          "content": {
            "type": "string"
          },
          "ttl": {
            "type": "integer",
            "description": "1 (automático) ou 60 a 86400; padrão 1."
          },
          "priority": {
            "type": "integer",
            "description": "Obrigatório só no MX."
          },
          "proxied": {
            "type": "boolean"
          },
          "comment": {
            "type": "string",
            "maxLength": 60
          }
        }
      },
      "platform_DnsRecordUpdate": {
        "type": "object",
        "minProperties": 1,
        "additionalProperties": false,
        "description": "`type` e `name` são imutáveis.",
        "properties": {
          "content": {
            "type": "string"
          },
          "ttl": {
            "type": "integer"
          },
          "priority": {
            "type": "integer"
          },
          "proxied": {
            "type": "boolean"
          },
          "comment": {
            "type": "string",
            "maxLength": 60
          }
        }
      },
      "platform_DnsPlan": {
        "type": "object",
        "description": "Resultado de `dry_run`: nada foi alterado na Cloudflare.",
        "required": [
          "dry_run",
          "action"
        ],
        "properties": {
          "dry_run": {
            "type": "boolean",
            "const": true
          },
          "action": {
            "type": "string",
            "enum": [
              "create",
              "update",
              "delete"
            ]
          },
          "before": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/platform_DnsRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "after": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/platform_DnsRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "requires_confirmation": {
            "type": "boolean",
            "description": "Se verdadeiro, a escrita real exige `X-Asender-Confirmar`."
          },
          "confirm": {
            "type": "string",
            "description": "O valor exato a enviar em `X-Asender-Confirmar`."
          }
        }
      },
      "platform_DnsChange": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "connection_id": {
            "type": "string"
          },
          "zone_id": {
            "type": "string"
          },
          "zone_name": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "enum": [
              "create",
              "update",
              "delete"
            ]
          },
          "before": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/platform_DnsRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "after": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/platform_DnsRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "actor": {
            "type": "string",
            "description": "Id da chave ou do usuário."
          },
          "source": {
            "type": "string",
            "enum": [
              "tela",
              "api",
              "mcp"
            ]
          },
          "trace_id": {
            "type": "string"
          },
          "result": {
            "type": "string",
            "enum": [
              "ok",
              "failed",
              "dry_run"
            ]
          },
          "error": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_DnsChanges": {
        "type": "object",
        "required": [
          "changes"
        ],
        "properties": {
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/platform_DnsChange"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          }
        }
      },
      "platform_DnsProblema": {
        "type": "object",
        "description": "RFC 9457 do serviço de DNS (rotas de painel).",
        "required": [
          "type",
          "title",
          "status",
          "codigo"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "codigo": {
            "type": "string",
            "description": "Identificador estável (PascalCase)."
          },
          "instance": {
            "type": "string"
          },
          "trace_id": {
            "type": "string"
          }
        }
      },
      "platform_Webhook": {
        "type": "object",
        "required": [
          "Id",
          "Url",
          "Events",
          "Active"
        ],
        "properties": {
          "Id": {
            "type": "string",
            "examples": [
              "whk_83fcaff4b496c6e6"
            ]
          },
          "Url": {
            "type": "string",
            "format": "uri"
          },
          "Events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Padrões assinados: `*`, `<família>.*` ou tipo exato do catálogo."
          },
          "Active": {
            "type": "boolean"
          },
          "ConsecutiveFailures": {
            "type": "integer"
          },
          "LastDeliveryAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_WebhookCreate": {
        "type": "object",
        "required": [
          "Url",
          "Events"
        ],
        "additionalProperties": false,
        "properties": {
          "Url": {
            "type": "string",
            "maxLength": 2048,
            "description": "https em produção; sem destino interno."
          },
          "Events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 32,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "examples": [
                "*",
                "email.*",
                "email.failed"
              ],
              "description": "`*`, `email.*`, `sms.*`, `whatsapp.*`, `push.*`, `mail.*` ou um de: email.queued, email.failed, sms.sent, sms.failed, whatsapp.sent, whatsapp.failed, push.delivered, push.failed, mail.sent, mail.received."
            }
          },
          "Active": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "platform_WebhookUpdate": {
        "type": "object",
        "minProperties": 1,
        "additionalProperties": false,
        "properties": {
          "Url": {
            "type": "string",
            "maxLength": 2048
          },
          "Events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 32,
            "uniqueItems": true,
            "items": {
              "type": "string"
            }
          },
          "Active": {
            "type": "boolean"
          }
        }
      },
      "platform_WebhookCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/platform_RequestIdEnvelope"
          },
          {
            "$ref": "#/components/schemas/platform_Webhook"
          },
          {
            "type": "object",
            "required": [
              "Secret"
            ],
            "properties": {
              "Secret": {
                "type": "string",
                "examples": [
                  "whsec_0123456789abcdef"
                ],
                "description": "Segredo de assinatura. Aparece SÓ nesta resposta."
              },
              "PlaintextWarning": {
                "type": "string"
              }
            }
          }
        ]
      },
      "platform_WebhookTestResult": {
        "allOf": [
          {
            "$ref": "#/components/schemas/platform_RequestIdEnvelope"
          },
          {
            "type": "object",
            "required": [
              "WebhookId",
              "EventId",
              "EventType",
              "Delivered",
              "StatusCode",
              "Reason",
              "DurationMs"
            ],
            "properties": {
              "WebhookId": {
                "type": "string"
              },
              "EventId": {
                "type": "string",
                "description": "Também vai em `Asender-Webhook-Id`."
              },
              "EventType": {
                "type": "string",
                "const": "webhook.test"
              },
              "Delivered": {
                "type": "boolean"
              },
              "StatusCode": {
                "type": "integer"
              },
              "Reason": {
                "type": "string",
                "enum": [
                  "",
                  "non_2xx",
                  "transport_error",
                  "destination_blocked",
                  "non_production_blocked"
                ]
              },
              "DurationMs": {
                "type": "integer"
              }
            }
          }
        ]
      },
      "platform_Device": {
        "type": "object",
        "properties": {
          "Id": {
            "type": "string"
          },
          "TenantId": {
            "type": "string"
          },
          "ContactId": {
            "type": "string"
          },
          "Token": {
            "type": "string"
          },
          "Platform": {
            "type": "string",
            "enum": [
              "ios",
              "android",
              "web"
            ]
          },
          "Active": {
            "type": "boolean"
          },
          "LastSeenAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_Tenant": {
        "type": "object",
        "description": "Projeção do asender_plataforma_api (`tenantPayload`), pascalizada.",
        "properties": {
          "Id": {
            "type": "string"
          },
          "Name": {
            "type": "string"
          },
          "Slug": {
            "type": "string"
          },
          "Status": {
            "type": "string",
            "enum": [
              "active",
              "suspended"
            ]
          },
          "TrialEndsAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "Metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Valor opaco."
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_TenantNode": {
        "type": "object",
        "description": "Nó de árvore (`tenantNodePayload` do core), pascalizado.",
        "properties": {
          "Id": {
            "type": "string"
          },
          "Name": {
            "type": "string"
          },
          "Slug": {
            "type": "string"
          },
          "Status": {
            "type": "string"
          },
          "Kind": {
            "type": "string",
            "enum": [
              "root",
              "leaf"
            ]
          },
          "Depth": {
            "type": "integer",
            "description": "Profundidade absoluta na árvore."
          },
          "RelativeDepth": {
            "type": "integer",
            "description": "Profundidade relativa ao nó consultado — use para indentar."
          },
          "ParentId": {
            "type": [
              "string",
              "null"
            ]
          },
          "MonthlyQuota": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`null` = herda."
          },
          "EffectiveQuota": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Valor que realmente vale; `null` = sem limite em nenhum ancestral."
          },
          "ChildrenCount": {
            "type": "integer"
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_Usage": {
        "type": "object",
        "properties": {
          "Period": {
            "type": "string",
            "examples": [
              "2026-07"
            ]
          },
          "EmailsSent": {
            "type": "integer"
          },
          "SmsSent": {
            "type": "integer"
          },
          "PushSent": {
            "type": "integer"
          },
          "ApiCalls": {
            "type": "integer"
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "platform_ReportCounters": {
        "type": "object",
        "description": "`Total` é o volume do período; `Queued` é BACKLOG (o que ainda não saiu).",
        "properties": {
          "Total": {
            "type": "integer"
          },
          "Queued": {
            "type": "integer"
          },
          "Sent": {
            "type": "integer"
          },
          "Delivered": {
            "type": "integer"
          },
          "Bounced": {
            "type": "integer"
          },
          "Failed": {
            "type": "integer"
          },
          "Opened": {
            "type": "integer"
          },
          "Clicked": {
            "type": "integer"
          }
        }
      },
      "platform_ReportRates": {
        "type": "object",
        "description": "`Delivery = Delivered/Sent`, `Bounce = Bounced/Sent`,\n`Open = Opened/Delivered`, `Click = Clicked/Delivered`.\nDenominador zero devolve `0` (nunca `null`, nunca `NaN`).",
        "properties": {
          "Delivery": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "Bounce": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "Open": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "Click": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          }
        }
      },
      "platform_ReportPeriod": {
        "type": "object",
        "description": "Janela efetivamente usada, já com o default aplicado. `To` é EXCLUSIVO.",
        "properties": {
          "From": {
            "type": "string",
            "format": "date-time"
          },
          "To": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "mail_ContaGerenciada": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "estado": {
            "type": "string"
          },
          "bloqueada": {
            "type": "boolean"
          },
          "gerenciada_por_tenant": {
            "type": "string"
          },
          "ultimo_acesso_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "mail_MotivoDaAcao": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "motivo"
        ],
        "properties": {
          "motivo": {
            "type": "string",
            "minLength": 5,
            "maxLength": 500,
            "description": "razão da ação",
            "gravada na trilha": null
          }
        }
      },
      "mail_ResultadoDaAcao": {
        "type": "object",
        "properties": {
          "conta": {
            "type": "object"
          },
          "senha_temporaria": {
            "type": "string",
            "description": "só em senha-temporaria; mostrada UMA vez"
          },
          "sessoes_encerradas": {
            "type": "integer"
          },
          "removidos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reenrolment_obrigatorio": {
            "type": "boolean"
          },
          "revogadas": {
            "type": "integer"
          }
        }
      },
      "mail_EnderecoRetido": {
        "type": "object",
        "description": "Um endereço sem caixa que recebeu e-mail e está retido na quarentena.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "endereco": {
            "type": "string"
          },
          "dominio": {
            "type": "string"
          },
          "domain_id": {
            "type": "string",
            "format": "uuid"
          },
          "local_part": {
            "type": "string"
          },
          "estado": {
            "type": "string",
            "enum": [
              "pendente",
              "aceita",
              "descartada"
            ]
          },
          "mensagens": {
            "type": "integer",
            "description": "mensagens ainda retidas"
          },
          "bytes": {
            "type": "integer",
            "format": "int64"
          },
          "remetentes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "até 10 remetentes distintos"
          },
          "primeiro_em": {
            "type": "string",
            "format": "date-time"
          },
          "ultimo_em": {
            "type": "string",
            "format": "date-time"
          },
          "total_recebidas": {
            "type": "integer",
            "description": "total que já chegou para o endereço"
          },
          "mailbox_id": {
            "type": "string",
            "format": "uuid",
            "description": "a caixa criada",
            "quando aceito": null
          }
        }
      },
      "mail_MensagemRetida": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "estado": {
            "type": "string",
            "enum": [
              "retida",
              "entregue",
              "descartada"
            ]
          },
          "mail_from": {
            "type": "string"
          },
          "remetente": {
            "type": "string"
          },
          "assunto": {
            "type": "string"
          },
          "tamanho": {
            "type": "integer",
            "format": "int64"
          },
          "recebido_em": {
            "type": "string",
            "format": "date-time"
          },
          "av_status": {
            "type": "string"
          },
          "av_assinatura": {
            "type": "string"
          }
        }
      },
      "mail_ResultadoAceiteQuarentena": {
        "type": "object",
        "properties": {
          "endereco": {
            "type": "string"
          },
          "mailbox_id": {
            "type": "string",
            "format": "uuid"
          },
          "caixa_criada": {
            "type": "boolean"
          },
          "movidas": {
            "type": "integer"
          },
          "restam": {
            "type": "integer",
            "description": "ainda retidas (0 = endereço fechado como aceita)"
          }
        }
      },
      "mail_DrenagemQuarentena": {
        "type": "object",
        "description": "O que ligar o autocriar fez com a quarentena do domínio.",
        "properties": {
          "enderecos": {
            "type": "integer"
          },
          "mensagens": {
            "type": "integer"
          },
          "falhas": {
            "type": "integer"
          },
          "restantes": {
            "type": "integer",
            "description": "endereços além do limite por chamada; chame de novo"
          }
        }
      },
      "mail_EstadoDNS": {
        "type": "object",
        "description": "Retrato da verificação de DNS de um domínio. `cliente` é o grupo \"clientes de e-mail\" (CNAME `autoconfig` + SRV `_imaps`/`_submissions`): é OPCIONAL, não entra no veredito `pronto` e não bloqueia a verificação.",
        "properties": {
          "posse": {
            "type": "boolean"
          },
          "mx": {
            "type": "boolean"
          },
          "spf": {
            "type": "boolean"
          },
          "dkim": {
            "type": "boolean"
          },
          "dmarc": {
            "type": "boolean"
          },
          "cliente": {
            "type": "boolean",
            "description": "o CNAME autoconfig e os SRV de IMAP/SMTP estão publicados"
          }
        }
      },
      "mail_RegistroDNS": {
        "type": "object",
        "description": "Um registro a publicar. Em `SRV`, `valor` é `prio peso porta alvo.` (formato BIND); os registros do grupo clientes de e-mail (CNAME `autoconfig` e SRV) só aparecem no modo `completo`.",
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "TXT",
              "MX",
              "CNAME",
              "SRV"
            ]
          },
          "host": {
            "type": "string"
          },
          "valor": {
            "type": "string"
          },
          "prioridade": {
            "type": "integer"
          },
          "explica": {
            "type": "string"
          }
        }
      },
      "mail_AcessoCliente": {
        "type": "object",
        "description": "Onde e como configurar um cliente de e-mail (Thunderbird, Outlook, celular) numa caixa. Nunca traz segredo: a senha de aplicativo nasce no auth.",
        "required": [
          "endereco",
          "usuario",
          "dominio",
          "dominio_verificado",
          "registros_cliente",
          "tem_grant",
          "imap",
          "smtp"
        ],
        "properties": {
          "endereco": {
            "type": "string"
          },
          "usuario": {
            "type": "string",
            "description": "o login do cliente de e-mail (o endereço completo)"
          },
          "dominio": {
            "type": "string"
          },
          "dominio_verificado": {
            "type": "boolean"
          },
          "registros_cliente": {
            "type": "boolean",
            "description": "o DNS de configuração automática do domínio está publicado"
          },
          "tem_grant": {
            "type": "boolean",
            "description": "a pessoa tem concessão nesta caixa (o IMAP só autoriza por concessão)"
          },
          "imap": {
            "type": "object",
            "properties": {
              "host": {
                "type": "string"
              },
              "porta": {
                "type": "integer",
                "example": 993
              },
              "seguranca": {
                "type": "string",
                "enum": [
                  "ssl_tls"
                ]
              }
            }
          },
          "smtp": {
            "type": "object",
            "properties": {
              "host": {
                "type": "string"
              },
              "portas": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "porta": {
                      "type": "integer",
                      "example": 465
                    },
                    "seguranca": {
                      "type": "string",
                      "enum": [
                        "ssl_tls",
                        "starttls"
                      ]
                    }
                  }
                }
              }
            }
          },
          "autoconfig_host": {
            "type": "string"
          },
          "senha_app_url": {
            "type": "string",
            "description": "link para criar a senha de aplicativo no auth (vazio sem host do auth)"
          }
        }
      },
      "mail_EstadoCloudflare": {
        "type": "object",
        "description": "O que a tela pode oferecer para o domínio na Cloudflare. O token NUNCA volta (nem cifrado). `domain_connect.motivo` explica por que o botão de 1 clique não aparece — consulta_falhou | fora_da_cloudflare | sem_domain_connect | sem_chave | template_pendente.",
        "properties": {
          "zona": {
            "type": "string",
            "example": "loja.com.br"
          },
          "na_cloudflare": {
            "type": "boolean"
          },
          "consulta_falhou": {
            "type": "boolean"
          },
          "domain_connect": {
            "type": "object",
            "properties": {
              "disponivel": {
                "type": "boolean"
              },
              "motivo": {
                "type": "string",
                "enum": [
                  "consulta_falhou",
                  "fora_da_cloudflare",
                  "sem_domain_connect",
                  "sem_chave",
                  "template_pendente"
                ]
              }
            }
          },
          "token": {
            "type": "object",
            "properties": {
              "disponivel": {
                "type": "boolean",
                "description": "o ambiente tem a chave de cifra (ENCRYPTION_KEY)"
              },
              "configurado": {
                "type": "boolean"
              },
              "zona": {
                "type": "string"
              },
              "atualizado_em": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "ultima_aplicacao": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/mail_AplicacaoDNS"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "mail_AplicacaoDNS": {
        "type": "object",
        "description": "Uma linha da trilha de auditoria (append-only) das ações no DNS.",
        "properties": {
          "ator": {
            "type": "string"
          },
          "metodo": {
            "type": "string",
            "enum": [
              "token",
              "domain_connect"
            ]
          },
          "evento": {
            "type": "string",
            "enum": [
              "token_salvo",
              "token_removido",
              "aplicacao",
              "url_gerada"
            ]
          },
          "zona": {
            "type": "string"
          },
          "resultado": {
            "type": "string",
            "enum": [
              "ok",
              "parcial",
              "erro"
            ]
          },
          "registros": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/mail_AcaoDNS"
            }
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "mail_CaixaComTipo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "domain_id": {
            "type": "string",
            "format": "uuid"
          },
          "local_part": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "quota_bytes": {
            "type": "integer"
          },
          "active": {
            "type": "boolean"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "pessoal",
              "coletiva"
            ]
          },
          "origem": {
            "type": "string",
            "enum": [
              "manual",
              "automatica",
              "conta_iam",
              "legado"
            ],
            "readOnly": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "mail_RecusaAssinatura": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "CaixaInelegivel",
              "CaixaAssinada",
              "EfeitoExternoBloqueado",
              "AnexoInfectado"
            ]
          },
          "message": {
            "type": "string"
          },
          "motivo": {
            "type": "string",
            "enum": [
              "pessoal",
              "origem_conta_iam",
              "origem_desconhecida",
              "catch_all",
              "caixa_inativa",
              "dominio_inativo",
              "dominio_nao_verificado",
              "dominio_somente_envio",
              "dominio_divergente"
            ]
          }
        }
      },
      "mail_CaixaElegivel": {
        "type": "object",
        "required": [
          "id",
          "endereco",
          "display_name",
          "tipo",
          "origem",
          "elegivel",
          "assinada"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "endereco": {
            "type": "string",
            "example": "sac@empresa.test.asender.com.br"
          },
          "display_name": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "pessoal",
              "coletiva"
            ]
          },
          "origem": {
            "type": "string",
            "enum": [
              "manual",
              "automatica",
              "conta_iam",
              "legado"
            ]
          },
          "elegivel": {
            "type": "boolean"
          },
          "motivo": {
            "type": "string"
          },
          "assinada": {
            "type": "boolean"
          }
        }
      },
      "mail_AssinaturaDeCaixa": {
        "type": "object",
        "required": [
          "id",
          "mailbox_id",
          "endereco",
          "display_name",
          "assinante",
          "criada_por",
          "criada_em"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "mailbox_id": {
            "type": "string",
            "format": "uuid"
          },
          "endereco": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "assinante": {
            "type": "string",
            "enum": [
              "atendimento"
            ]
          },
          "criada_por": {
            "type": "string",
            "description": "sub de quem ligou"
          },
          "criada_em": {
            "type": "string",
            "format": "date-time"
          },
          "revogada_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revogada_por": {
            "type": "string"
          }
        }
      },
      "mail_MensagemAssinada": {
        "type": "object",
        "required": [
          "id",
          "mailbox_id",
          "direction",
          "message_id",
          "from_email",
          "subject",
          "received_at",
          "anexos"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "mailbox_id": {
            "type": "string",
            "format": "uuid"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound"
            ]
          },
          "message_id": {
            "type": "string",
            "description": "Message-ID RFC 5322"
          },
          "thread_id": {
            "type": "string"
          },
          "in_reply_to": {
            "type": "string"
          },
          "from_email": {
            "type": "string"
          },
          "from_name": {
            "type": "string"
          },
          "to_emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "cc_emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "subject": {
            "type": "string"
          },
          "body_text": {
            "type": "string"
          },
          "body_html": {
            "type": "string",
            "description": "HTML já sanitizado pelo mail",
            "imagens inline embutidas": null
          },
          "received_at": {
            "type": "string",
            "format": "date-time"
          },
          "anexos": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "filename",
                "content_type",
                "size_bytes"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "filename": {
                  "type": "string"
                },
                "content_type": {
                  "type": "string"
                },
                "size_bytes": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "mail_EnvioDoAssinante": {
        "type": "object",
        "required": [
          "to"
        ],
        "additionalProperties": false,
        "properties": {
          "to": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            }
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "subject": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "in_reply_to_id": {
            "type": "string",
            "format": "uuid",
            "description": "id NO MAIL da mensagem do cliente"
          },
          "attachments": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "filename",
                "content_type",
                "content_base64"
              ],
              "properties": {
                "filename": {
                  "type": "string"
                },
                "content_type": {
                  "type": "string"
                },
                "content_base64": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "mail_ResultadoEnvioAssinante": {
        "type": "object",
        "required": [
          "message_id",
          "status"
        ],
        "properties": {
          "message_id": {
            "type": "string"
          },
          "sent_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "sent"
            ]
          },
          "idempotent_replay": {
            "type": "boolean"
          }
        }
      },
      "mail_AcaoDNS": {
        "type": "object",
        "description": "O que a aplicação fez num registro. Nunca há \"apagado\": registro alheio que atrapalha vira `conflito` com a instrução em `detalhe`.",
        "properties": {
          "resultado": {
            "type": "string",
            "enum": [
              "mantido",
              "criado",
              "atualizado",
              "conflito",
              "falhou"
            ]
          },
          "tipo": {
            "type": "string",
            "example": "CNAME"
          },
          "nome": {
            "type": "string"
          },
          "valor": {
            "type": "string"
          },
          "papel": {
            "type": "string"
          },
          "anterior": {
            "type": "string",
            "description": "valor antes de um `atualizado` (para desfazer à mão)"
          },
          "detalhe": {
            "type": "string"
          }
        }
      },
      "mail_RelatorioCloudflare": {
        "type": "object",
        "properties": {
          "zona": {
            "type": "string"
          },
          "ok": {
            "type": "boolean",
            "description": "nenhum conflito nem falha"
          },
          "registros": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/mail_AcaoDNS"
            }
          }
        }
      },
      "mail_ResumoDeFerramenta": {
        "type": "object",
        "description": "Contrato comum de resumo de ferramenta (plano 2026-10-02) — o mesmo formato em toda ferramenta.",
        "required": [
          "ferramenta",
          "periodo",
          "indicadores",
          "series"
        ],
        "properties": {
          "ferramenta": {
            "type": "string",
            "description": "slug da ferramenta (crm, mail, pages, atendimento, …)"
          },
          "periodo": {
            "type": "object",
            "required": [
              "de",
              "ate",
              "chave"
            ],
            "properties": {
              "de": {
                "type": "string",
                "format": "date",
                "description": "primeiro dia da janela (inclusivo)"
              },
              "ate": {
                "type": "string",
                "format": "date",
                "description": "último dia da janela (inclusivo)"
              },
              "chave": {
                "type": "string",
                "enum": [
                  "7d",
                  "30d",
                  "90d"
                ]
              }
            }
          },
          "indicadores": {
            "type": "array",
            "minItems": 1,
            "maxItems": 6,
            "items": {
              "$ref": "#/components/schemas/mail_ResumoIndicador"
            }
          },
          "series": {
            "type": "array",
            "maxItems": 2,
            "items": {
              "$ref": "#/components/schemas/mail_ResumoSerie"
            }
          }
        }
      },
      "mail_ResumoIndicador": {
        "type": "object",
        "required": [
          "chave",
          "rotulo",
          "valor",
          "anterior",
          "formato",
          "link"
        ],
        "properties": {
          "chave": {
            "type": "string",
            "description": "id estável do indicador"
          },
          "rotulo": {
            "type": "string",
            "description": "rótulo pt-BR (o front pode traduzir pela chave)"
          },
          "valor": {
            "type": "number"
          },
          "anterior": {
            "type": [
              "number",
              "null"
            ],
            "description": "mesmo indicador na janela anterior de mesmo tamanho; null = sem comparação"
          },
          "formato": {
            "type": "string",
            "enum": [
              "numero",
              "percentual",
              "moeda",
              "duracao"
            ],
            "description": "moeda em centavos BRL; duracao em segundos; percentual 0–100"
          },
          "link": {
            "type": "string",
            "description": "caminho relativo dentro da ferramenta, já com a conta"
          }
        }
      },
      "mail_ResumoSerie": {
        "type": "object",
        "required": [
          "chave",
          "rotulo",
          "tipo",
          "pontos"
        ],
        "properties": {
          "chave": {
            "type": "string"
          },
          "rotulo": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "linha",
              "barra"
            ]
          },
          "pontos": {
            "type": "array",
            "description": "um ponto por dia da janela (dia sem evento vem com 0)",
            "items": {
              "type": "object",
              "required": [
                "data",
                "valor"
              ],
              "properties": {
                "data": {
                  "type": "string",
                  "format": "date"
                },
                "valor": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "mail_Erro": {
        "type": "object",
        "description": "Envelope único de erro da Mail API.",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "classe do erro (ex.: ValidationError, AuthorizationError, NotFound, QuotaExceeded, SendFailed)"
          },
          "message": {
            "type": "string",
            "description": "mensagem legível; nunca revela existência de recurso de outra conta"
          }
        }
      },
      "mail_Email": {
        "type": "object",
        "description": "Corpo de `POST /v1/emails`.",
        "required": [
          "from",
          "to"
        ],
        "properties": {
          "from": {
            "type": "string",
            "format": "email",
            "description": "endereço de uma CAIXA existente da conta (o domínio verificado não basta); o tenant tem de ser dono",
            "examples": [
              "joana@empresa.com.br"
            ]
          },
          "to": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            },
            "minItems": 1
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "bcc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "subject": {
            "type": "string"
          },
          "text": {
            "type": "string",
            "description": "corpo em texto puro"
          },
          "html": {
            "type": "string",
            "description": "corpo HTML (sanitizado no servidor)"
          },
          "in_reply_to_id": {
            "type": "string",
            "description": "id interno da mensagem respondida (threading), opcional"
          },
          "identity_id": {
            "type": "string",
            "format": "uuid",
            "description": "identidade de envio (send-as) da caixa — o webmail (/mailboxes/{id}/send). O endereço dela precisa ser de um domínio da org com DKIM verificado; senão 422. Opcional."
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/mail_Anexo"
            }
          }
        }
      },
      "mail_Anexo": {
        "type": "object",
        "required": [
          "filename",
          "content"
        ],
        "properties": {
          "filename": {
            "type": "string"
          },
          "content_type": {
            "type": "string",
            "description": "MIME; inferido do nome se ausente"
          },
          "content": {
            "type": "string",
            "format": "byte",
            "description": "conteúdo em base64"
          }
        }
      },
      "mail_Etiqueta": {
        "type": "object",
        "description": "Corpo de criação/edição de etiqueta (M-E).",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "1 a 60 caracteres, único na caixa"
          },
          "color": {
            "type": "string",
            "description": "rótulo simbólico da paleta (ex.: red, blue); vazio = sem cor"
          }
        }
      },
      "mail_EtiquetaComContagem": {
        "type": "object",
        "description": "Etiqueta da caixa com origem e contagens — a lateral do webmail (ADR-0087, frente C).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "mailbox_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "description": "nome com '/' é desenhado como grupo na tela (sem hierarquia no banco)"
          },
          "color": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "origem": {
            "type": "string",
            "enum": [
              "usuario",
              "sistema",
              "org"
            ]
          },
          "sistema": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "importante",
              "lista",
              null
            ],
            "description": "chave da etiqueta de sistema; não renomeia nem apaga"
          },
          "total": {
            "type": "integer",
            "description": "mensagens com a etiqueta (todas as pastas); ausente = 0"
          },
          "nao_lidas": {
            "type": "integer",
            "description": "dessas, as não lidas; ausente = 0"
          }
        }
      },
      "mail_LoteDeEtiquetas": {
        "type": "object",
        "description": "Etiquetas a aplicar/remover. Ids de mensagem ou etiqueta de OUTRA caixa são ignorados, nunca vinculados.",
        "properties": {
          "message_ids": {
            "type": "array",
            "maxItems": 500,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "adicionar": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "remover": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "mail_ResultadoDeEtiquetas": {
        "type": "object",
        "description": "O que MUDOU (não o que foi pedido).",
        "properties": {
          "vinculadas": {
            "type": "integer"
          },
          "desvinculadas": {
            "type": "integer"
          }
        }
      },
      "mail_Identidade": {
        "type": "object",
        "description": "Corpo de criação/edição de identidade de envio (M-E).",
        "required": [
          "email"
        ],
        "properties": {
          "from_name": {
            "type": "string",
            "description": "nome de exibição do De"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "endereço De; no envio precisa ser de um domínio da org com DKIM verificado"
          },
          "is_default": {
            "type": "boolean",
            "description": "pré-seleciona no compositor; no máximo uma padrão por caixa"
          }
        }
      },
      "mail_EmailAceito": {
        "type": "object",
        "required": [
          "message_id",
          "status"
        ],
        "properties": {
          "message_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "sent"
            ],
            "examples": [
              "sent"
            ]
          },
          "idempotent_replay": {
            "type": "boolean",
            "description": "true quando a Idempotency-Key já havia sido usada; nada foi reenviado"
          }
        }
      },
      "mail_Pasta": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "inbox",
              "sent",
              "drafts",
              "archive",
              "spam",
              "trash",
              "custom"
            ]
          },
          "name": {
            "type": "string"
          },
          "total": {
            "type": "integer",
            "format": "int64"
          },
          "unread": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "mail_PatchDeMensagem": {
        "type": "object",
        "description": "Campo ausente não é alterado. Pelo menos um é obrigatório (corpo vazio = 400).",
        "properties": {
          "read": {
            "type": "boolean"
          },
          "starred": {
            "type": "boolean"
          },
          "folder": {
            "type": "string",
            "enum": [
              "inbox",
              "sent",
              "drafts",
              "archive",
              "spam",
              "trash"
            ],
            "description": "pasta de destino; criada sob demanda"
          }
        }
      },
      "mail_MensagemDaLista": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "from_email": {
            "type": "string"
          },
          "from_name": {
            "type": "string"
          },
          "to_emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "subject": {
            "type": "string"
          },
          "snippet": {
            "type": "string",
            "description": "começo do corpo em texto puro (200 caracteres)"
          },
          "has_attachment": {
            "type": "boolean"
          },
          "attachment_count": {
            "type": "integer"
          },
          "starred": {
            "type": "boolean"
          },
          "folder_kind": {
            "type": "string"
          },
          "folder_name": {
            "type": "string",
            "description": "nome da pasta (o da pessoa, ou o padrão da de sistema) — o selo de \"Todos os e-mails\""
          },
          "read": {
            "type": "boolean"
          },
          "received_at": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string"
          }
        }
      },
      "mail_PaginaDeMensagens": {
        "type": "object",
        "properties": {
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/mail_MensagemDaLista"
            }
          },
          "next_cursor": {
            "type": "string",
            "description": "vazio quando acabou; opaco — devolva como veio"
          },
          "total": {
            "type": "integer",
            "description": "só com `q` e sem cursor: quantas mensagens a busca acha na caixa (até 10 000)"
          },
          "total_limitado": {
            "type": "boolean",
            "description": "true quando há mais que 10 000"
          }
        }
      },
      "mail_RegraDaBusca": {
        "type": "object",
        "description": "A busca convertida numa regra para o construtor (ações vazias — a pessoa escolhe).",
        "properties": {
          "filtro": {
            "$ref": "#/components/schemas/mail_FiltroV2"
          },
          "pronta": {
            "type": "boolean",
            "description": "há ao menos uma condição"
          },
          "pastas": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "escopo de \"aplicar às existentes\" (kinds ou ids)"
          },
          "nao_convertidos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "termo": {
                  "type": "string"
                },
                "motivo": {
                  "type": "string"
                }
              }
            }
          },
          "busca_convertivel": {
            "type": "string",
            "description": "a busca só com os termos que viraram regra"
          },
          "total_busca": {
            "type": "integer",
            "description": "resultados da busca convertível no escopo — a prévia da regra dá o mesmo número"
          },
          "total_limitado": {
            "type": "boolean"
          }
        }
      },
      "mail_Parecidos": {
        "type": "object",
        "properties": {
          "selecionadas": {
            "type": "integer",
            "description": "mensagens analisadas (as desta caixa)"
          },
          "busca_sugerida": {
            "type": "string"
          },
          "criterios": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "chave": {
                  "type": "string",
                  "enum": [
                    "remetente",
                    "remetentes",
                    "local",
                    "dominio",
                    "dominios",
                    "dominio_registravel",
                    "lista",
                    "listas",
                    "assunto_prefixo",
                    "assunto_palavras",
                    "para",
                    "para_dominio",
                    "cc",
                    "responder_para",
                    "anexo"
                  ]
                },
                "familia": {
                  "type": "string",
                  "enum": [
                    "remetente",
                    "lista",
                    "assunto",
                    "destinatario",
                    "anexo",
                    "outros"
                  ]
                },
                "rotulo": {
                  "type": "string"
                },
                "busca": {
                  "type": "string",
                  "description": "vazio = não vira busca (ver motivo)"
                },
                "motivo": {
                  "type": "string"
                },
                "cobertura": {
                  "type": "integer"
                },
                "especificidade": {
                  "type": "integer"
                },
                "sugerido": {
                  "type": "boolean"
                },
                "total": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "mail_Rascunho": {
        "type": "object",
        "description": "Um rascunho é uma mensagem status='draft' na pasta drafts.",
        "properties": {
          "to": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "bcc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "subject": {
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "in_reply_to_id": {
            "type": "string"
          }
        }
      },
      "mail_Contato": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "mail_Assinatura": {
        "type": "object",
        "properties": {
          "html": {
            "type": "string",
            "description": "HTML da assinatura; sanitizado no servidor (bluemonday) na gravação"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "mail_PoliticaDaOrg": {
        "type": "object",
        "description": "Políticas de verificação da organização (ADR-0087). Ausência de configuração = os padrões abaixo. O PUT SUBSTITUI a política inteira: campo ausente volta ao padrão.",
        "properties": {
          "org_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "description": "vem da rota; um valor no corpo é ignorado"
          },
          "spam_limiar": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 2,
            "maximum": 30,
            "description": "pontuação a partir da qual vai para a Spam; null = o limiar do serviço (5)"
          },
          "spam_purga_dias": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "default": 30,
            "description": "dias NA PASTA Spam antes de apagar (conta desde que a mensagem entrou nela)"
          },
          "quarentena_purga_dias": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "default": 30
          },
          "av_heuristica": {
            "type": "string",
            "enum": [
              "quarentena",
              "marcar"
            ],
            "default": "quarentena",
            "description": "achado heurístico/PUA do antivírus: reter na Quarentena ou só marcar"
          },
          "encaminhamento_externo": {
            "type": "boolean",
            "default": false,
            "description": "regra de filtro pode encaminhar para fora da organização"
          }
        }
      },
      "mail_RemetenteDaOrg": {
        "type": "object",
        "description": "Remetente permitido (confia=true) ou bloqueado pela organização. Precedência na entrega: lista pessoal da caixa > lista da org > pontuação. Endereço exato vence domínio; domínio mais específico vence o pai (`@a.loja.com` antes de `@loja.com`).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "padrao": {
            "type": "string",
            "examples": [
              "@fornecedor.com.br",
              "nf@fornecedor.com.br"
            ],
            "description": "endereço ou @domínio, normalizado em minúsculas"
          },
          "confia": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "mail_VereditoAV": {
        "type": "object",
        "description": "O veredito do antivírus (ADR-0087). Também volta em GET /mailboxes/{id}/messages/{msgId} como `av_status`/`av_assinatura`. `nao_verificado` = nenhum scanner olhou (não é \"limpo\").",
        "properties": {
          "av_status": {
            "type": "string",
            "enum": [
              "nao_verificado",
              "pendente",
              "limpo",
              "infectado"
            ]
          },
          "av_assinatura": {
            "type": [
              "string",
              "null"
            ]
          },
          "av_motor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "mail_CondicaoDeRegra": {
        "type": "object",
        "required": [
          "campo",
          "operador",
          "valor"
        ],
        "description": "Uma condição do motor de regras v2 (ADR-0087). campo ∈ de, para, cc, qualquer_destinatario, assunto, corpo, tem_anexo, list_id, cabecalho:<Nome>, tamanho, anexo_nome, anexo_tipo, spam_score, av_status, recebida_em, dominio_de. operador ∈ contem, nao_contem, igual, comeca_com, termina_com, regex (RE2, ≤256), dominio (aceita lista \"a.com,b.com\"), um_de / contem_um_de (lista por vírgula, ≤20), maior_que, menor_que, existe, nao_existe — cada campo aceita só os seus. `valor` sem CR/LF (≤512). Nos campos de endereço (de, dominio_de, para, cc, qualquer_destinatario), `igual`/`um_de` com um item que é DOMÍNIO (\"x.com\" ou \"@x.com\") casam o domínio ou qualquer subdomínio dele — a mesma semântica da busca (`de:x.com`); item que é endereço continua igualdade exata.",
        "properties": {
          "campo": {
            "type": "string",
            "examples": [
              "list_id",
              "cabecalho:X-Mailer"
            ]
          },
          "operador": {
            "type": "string",
            "examples": [
              "regex"
            ]
          },
          "valor": {
            "type": "string",
            "maxLength": 512
          }
        }
      },
      "mail_AcaoDeRegra": {
        "type": "object",
        "required": [
          "tipo"
        ],
        "description": "tipo ∈ mover (Entrada, Arquivo ou uma pasta da pessoa — nunca Spam, Lixeira, Quarentena; com várias regras movendo, vence a PRIMEIRA na ordem, org antes das pessoais), arquivar (= mover para o Arquivo: pula a Entrada), apagar (= Lixeira), marcar_lida, estrelar, marcar_importante, nao_eh_spam, marcar_spam, etiquetar, desetiquetar, encaminhar (só destino verificado; nunca retroativo), responder (anti-loop; nunca retroativo). `valor`: pasta; id de etiqueta (pessoal) ou nome (org); destinos separados por vírgula (≤5); texto da resposta.",
        "properties": {
          "tipo": {
            "type": "string"
          },
          "valor": {
            "type": "string"
          }
        }
      },
      "mail_FiltroV2": {
        "type": "object",
        "required": [
          "nome",
          "condicoes",
          "acoes"
        ],
        "description": "Regra pessoal (por caixa e pessoa). Todas as condições precisam casar; o primeiro destino vence.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "nome": {
            "type": "string",
            "maxLength": 80
          },
          "posicao": {
            "type": "integer",
            "readOnly": true
          },
          "ativo": {
            "type": "boolean"
          },
          "parar_aqui": {
            "type": "boolean"
          },
          "condicoes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/mail_CondicaoDeRegra"
            }
          },
          "acoes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/mail_AcaoDeRegra"
            }
          },
          "execucoes": {
            "type": "integer",
            "readOnly": true,
            "description": "quantas vezes a regra agiu"
          },
          "ultima_execucao": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true
          }
        }
      },
      "mail_RegraDaOrg": {
        "description": "Regra da organização — roda antes das pessoais; etiqueta por NOME, pasta só de sistema, sem encaminhar/responder.",
        "allOf": [
          {
            "$ref": "#/components/schemas/mail_FiltroV2"
          },
          {
            "type": "object",
            "properties": {
              "domain_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "null = todas as caixas da org"
              },
              "final": {
                "type": "boolean",
                "description": "depois desta",
                "as regras pessoais não rodam": null
              }
            }
          }
        ]
      },
      "mail_PreviaDeRegras": {
        "type": "object",
        "properties": {
          "examinadas": {
            "type": "integer"
          },
          "afetadas": {
            "type": "integer"
          },
          "restam": {
            "type": "boolean",
            "description": "havia mais mensagens além do teto de 5 000"
          },
          "amostra": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "assunto": {
                  "type": "string"
                },
                "de": {
                  "type": "string"
                },
                "recebida_em": {
                  "type": "string",
                  "format": "date-time"
                },
                "acoes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "tipos; os de efeito externo vêm como \"encaminhar:nao_retroativo\""
                },
                "destino": {
                  "type": "string",
                  "description": "a pasta onde a mensagem FICA (kind ou id da pasta da pessoa); vazio = não muda"
                }
              }
            }
          }
        }
      },
      "mail_JobDeRegras": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "fila",
              "rodando",
              "feito",
              "falhou",
              "cancelado"
            ]
          },
          "examinadas": {
            "type": "integer"
          },
          "afetadas": {
            "type": "integer"
          },
          "erro": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "terminado_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "mail_SugestaoDeRegra": {
        "type": "object",
        "properties": {
          "motivo": {
            "type": "string",
            "examples": [
              "142 mensagens da lista novidades.loja.example nos últimos 90 dias, 3% lidas"
            ]
          },
          "filtro": {
            "$ref": "#/components/schemas/mail_FiltroV2"
          }
        }
      },
      "mail_EventoDeAuditoriaDeRegra": {
        "type": "object",
        "properties": {
          "regra_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "user_id": {
            "type": "string"
          },
          "operacao": {
            "type": "string",
            "enum": [
              "criar",
              "editar",
              "apagar",
              "reordenar",
              "aplicar"
            ]
          },
          "antes": {
            "type": [
              "object",
              "null"
            ]
          },
          "depois": {
            "type": [
              "object",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "mail_ExecucaoDeRegra": {
        "type": "object",
        "properties": {
          "regra_id": {
            "type": "string",
            "format": "uuid"
          },
          "origem": {
            "type": "string",
            "enum": [
              "usuario",
              "org"
            ]
          },
          "nome": {
            "type": [
              "string",
              "null"
            ],
            "description": "null quando a regra foi apagada ou é regra pessoal de outra pessoa"
          },
          "momento": {
            "type": "string",
            "enum": [
              "entrega",
              "lote"
            ]
          },
          "acoes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/mail_AcaoDeRegra"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "mail_DestinoDeEncaminhamento": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string"
          },
          "verificado_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "mesma_org": {
            "type": "boolean"
          }
        }
      },
      "mail_CaixaAtribuivel": {
        "type": "object",
        "description": "Linha da busca de atribuíveis — só metadado de gestão.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "address": {
            "type": "string",
            "example": "sac@empresa.test.asender.com.br"
          },
          "domain_id": {
            "type": "string",
            "format": "uuid"
          },
          "domain": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "pessoal",
              "coletiva"
            ]
          },
          "origem": {
            "type": "string",
            "enum": [
              "manual",
              "automatica",
              "conta_iam",
              "legado"
            ]
          },
          "titulares": {
            "type": "integer",
            "description": "pessoas com grant ativo"
          },
          "pessoal_de_outra_pessoa": {
            "type": "boolean",
            "description": "caixa pessoal (conta_iam) da qual quem busca não é dono"
          }
        }
      },
      "mail_LoteGrantsCorpo": {
        "type": "object",
        "required": [
          "operacao",
          "usuarios",
          "alvo"
        ],
        "properties": {
          "operacao": {
            "type": "string",
            "enum": [
              "conceder",
              "revogar"
            ]
          },
          "usuarios": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "uniqueItems": true,
            "items": {
              "type": "string"
            },
            "description": "pessoas ATIVAS do tenant"
          },
          "papel": {
            "type": "string",
            "enum": [
              "owner",
              "delegate"
            ],
            "default": "delegate",
            "description": "só ao conceder"
          },
          "alvo": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "modo",
                  "caixas"
                ],
                "properties": {
                  "modo": {
                    "type": "string",
                    "enum": [
                      "caixas"
                    ]
                  },
                  "caixas": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "modo"
                ],
                "properties": {
                  "modo": {
                    "type": "string",
                    "enum": [
                      "todas"
                    ]
                  },
                  "dominio_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "tipo": {
                    "type": "string",
                    "enum": [
                      "pessoal",
                      "coletiva"
                    ]
                  },
                  "incluir_pessoais": {
                    "type": "boolean",
                    "description": "inclui caixas conta_iam (exige confirmar_pessoais)"
                  }
                }
              }
            ]
          },
          "confirmar_pessoais": {
            "type": "boolean"
          },
          "confirmar_acesso_proprio": {
            "type": "boolean",
            "description": "exigido ao conceder quando o próprio admin está entre as pessoas"
          }
        }
      },
      "mail_LotePrevia": {
        "type": "object",
        "properties": {
          "total_caixas": {
            "type": "integer"
          },
          "total_pares": {
            "type": "integer"
          },
          "a_alterar": {
            "type": "integer",
            "description": "pares que a execução mudaria"
          },
          "ja_tem": {
            "type": "integer",
            "description": "conceder: pares que já têm grant; revogar: pares sem grant (nada a fazer)"
          },
          "papel_diferente_mantido": {
            "type": "integer",
            "description": "pares que já têm grant com OUTRO papel — mantido, nunca alterado"
          },
          "pessoais_incluidas": {
            "type": "integer"
          },
          "lotes": {
            "type": "integer",
            "description": "nº de páginas da execução"
          },
          "confirmacao_esperada": {
            "type": "integer",
            "description": "valor de `confirmar_contagem` (== a_alterar)"
          },
          "amostra": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "properties": {
                "address": {
                  "type": "string"
                },
                "user_id": {
                  "type": "string"
                },
                "acao": {
                  "type": "string",
                  "enum": [
                    "conceder",
                    "revogar",
                    "pular"
                  ]
                }
              }
            }
          }
        }
      },
      "mail_LoteResultado": {
        "type": "object",
        "properties": {
          "lote_id": {
            "type": "string",
            "format": "uuid"
          },
          "processadas": {
            "type": "integer",
            "description": "caixas da página"
          },
          "concedidos": {
            "type": "integer"
          },
          "revogados": {
            "type": "integer"
          },
          "ja_tinham": {
            "type": "integer",
            "description": "pares pulados (conceder: já tinham; revogar: não tinham)"
          },
          "proximo": {
            "type": "string",
            "nullable": true,
            "description": "cursor para o `apos` da próxima chamada; nulo ao concluir"
          },
          "concluido": {
            "type": "boolean"
          }
        }
      },
      "auth_MeSegurancaEmailExternoBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 254
          }
        }
      },
      "auth_MeSegurancaCodigoBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "codigo"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "pattern": "^[0-9]{6}$"
          }
        }
      },
      "auth_MeSegurancaAdiamentos": {
        "type": "object",
        "properties": {
          "quantidade": {
            "type": "integer",
            "description": "Total de adiamentos da conta."
          },
          "seguidos": {
            "type": "integer",
            "description": "Adiamentos desde a última ativação."
          },
          "ultimo_em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "vira_link": {
            "type": "boolean",
            "description": "Com 3 seguidos o passo vira link na faixa, sem modal (D15)."
          }
        }
      },
      "auth_MeSegurancaAdiamento": {
        "type": "object",
        "properties": {
          "adiado": {
            "type": "boolean"
          },
          "reaparece": {
            "type": "string",
            "enum": [
              "proxima_sessao"
            ]
          },
          "adiamentos": {
            "$ref": "#/components/schemas/auth_MeSegurancaAdiamentos"
          }
        }
      },
      "auth_MeSegurancaMetodos": {
        "type": "object",
        "properties": {
          "contagem": {
            "type": "integer"
          },
          "tipos": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "senha",
                "passkey",
                "totp",
                "email_externo"
              ]
            }
          },
          "minimo_admin": {
            "type": "integer",
            "example": 2
          },
          "emails_externos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Mascarados."
          }
        }
      },
      "auth_MeSegurancaSituacao": {
        "type": "object",
        "description": "Situação de segurança da própria conta (ADR-0146 D8/D15/D17).",
        "properties": {
          "modo": {
            "type": "string",
            "enum": [
              false,
              "observar",
              "enforce"
            ]
          },
          "nivel_efetivo": {
            "type": "string",
            "enum": [
              "estrito",
              "reduzido"
            ]
          },
          "modo_reduzido": {
            "type": "boolean"
          },
          "limites": {
            "type": "object",
            "description": "Vazio no estrito; no reduzido: sessao_absoluta_horas, inatividade_horas, dispositivos_max, api_keys_permitidas, acoes_sensiveis_por_hora."
          },
          "segundo_fator_obrigatorio": {
            "type": "boolean"
          },
          "origem": {
            "type": "string"
          },
          "expira_em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "protegido": {
            "type": "boolean"
          },
          "admin_apto": {
            "type": "boolean"
          },
          "pode_adiar": {
            "type": "boolean"
          },
          "exige_cadastro": {
            "type": "boolean",
            "description": "Poder administrativo com < 2 métodos (D17): ativação obrigatória, sem 'depois'."
          },
          "ativacao_obrigatoria_admin": {
            "type": "boolean",
            "description": "Alias de exige_cadastro (compatibilidade com a U1)."
          },
          "metodos_verificados": {
            "type": "integer",
            "description": "Alias de metodos.contagem para protegido (compatibilidade com a U1)."
          },
          "metodos": {
            "$ref": "#/components/schemas/auth_MeSegurancaMetodos"
          },
          "adiamentos": {
            "$ref": "#/components/schemas/auth_MeSegurancaAdiamentos"
          },
          "somente_leitura_ate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "auth_SegurancaDecisao": {
        "type": "object",
        "description": "Decisão efetiva de 2FA da conta (ADR-0146).",
        "properties": {
          "modo": {
            "type": "string",
            "enum": [
              false,
              "observar",
              "enforce"
            ]
          },
          "nivel_efetivo": {
            "type": "string",
            "enum": [
              "estrito",
              "reduzido"
            ]
          },
          "segundo_fator_obrigatorio": {
            "type": "boolean"
          },
          "origem": {
            "type": "string"
          },
          "protegido": {
            "type": "boolean"
          },
          "pode_adiar": {
            "type": "boolean"
          },
          "expira_em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "admin_apto": {
            "type": "boolean"
          },
          "metodos_verificados": {
            "type": "integer"
          },
          "observado": {
            "type": "string",
            "description": "No modo observar, o nível que valeria em enforce."
          }
        }
      },
      "auth_SegurancaExcecao": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "escopo": {
            "type": "string",
            "enum": [
              "equipe",
              "usuario"
            ]
          },
          "user_id": {
            "type": "string"
          },
          "motivo": {
            "type": "string"
          },
          "criada_por": {
            "type": "string"
          },
          "criada_em": {
            "type": "string",
            "format": "date-time"
          },
          "expira_em": {
            "type": "string",
            "format": "date-time"
          },
          "revogada_em": {
            "type": "string",
            "format": "date-time"
          },
          "ativa": {
            "type": "boolean"
          }
        }
      },
      "auth_SegurancaVisao": {
        "type": "object",
        "properties": {
          "tenant_id": {
            "type": "string"
          },
          "modo": {
            "type": "string",
            "enum": [
              false,
              "observar",
              "enforce"
            ]
          },
          "efeito_ativo": {
            "type": "boolean"
          },
          "nivel_base": {
            "type": "string",
            "enum": [
              "estrito",
              "recomendado"
            ]
          },
          "configurada": {
            "type": "boolean"
          },
          "atualizado_por": {
            "type": "string"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          },
          "motivo": {
            "type": "string"
          },
          "excecoes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/auth_SegurancaExcecao"
            }
          },
          "contas_sem_fator": {
            "type": "integer"
          },
          "parametros_modo_reduzido": {
            "type": "object"
          },
          "compensacoes_aplicadas": {
            "type": "boolean"
          }
        }
      },
      "auth_ErrorEnvelope": {
        "type": "object",
        "required": [
          "Error",
          "RequestId"
        ],
        "properties": {
          "Error": {
            "type": "object",
            "required": [
              "Type",
              "Code",
              "Message"
            ],
            "properties": {
              "Type": {
                "type": "string",
                "enum": [
                  "Sender",
                  "Receiver"
                ]
              },
              "Code": {
                "type": "string",
                "examples": [
                  "InvalidRequest",
                  "InvalidInput",
                  "MissingToken",
                  "InvalidCredentials",
                  "InvalidToken",
                  "ExpiredToken",
                  "TwoFactorFailed",
                  "TwoFactorSuperseded",
                  "TwoFactorCodeExpired",
                  "TwoFactorMissing",
                  "NotFound",
                  "Conflict",
                  "NotReady",
                  "InternalError"
                ]
              },
              "Message": {
                "type": "string"
              },
              "Details": {}
            }
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_User": {
        "type": "object",
        "description": "Nunca traz `password_hash` nem o segredo TOTP.",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "usr_demo_owner"
            ]
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Migration 012: não-nulo só pelo caminho de exclusão de plataforma."
          },
          "deleted_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "delete_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "purge_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Quando o purge automático apaga de verdade (deleted_at + 30 dias)."
          }
        }
      },
      "auth_LoginResult": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "JWT de sessão. VAZIO quando `two_factor_required`."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "user_id": {
            "type": "string"
          },
          "two_factor_required": {
            "type": "boolean"
          },
          "two_factor_token": {
            "type": "string",
            "description": "TICKET de desafio 2FA (JWT curto, ~5 min, assinado). Presente **só**\nquando `two_factor_required:true`. Prova, no passo do código, que a\nsenha já foi apresentada neste login. É o valor que `POST\n/v1/2fa/check` EXIGE — o cliente o repassa, não o inspeciona."
          },
          "two_factor_method": {
            "type": "string",
            "enum": [
              "email",
              "totp"
            ],
            "description": "CANAL do segundo fator. `email` (padrão de toda conta): o login já\nenviou um código de 6 dígitos ao e-mail do usuário — a tela mostra\n\"enviamos um código\" e oferece reenvio (`POST /v1/2fa/email/resend`).\n`totp`: o usuário digita o código do app. Presente só com\n`two_factor_required:true`. Conta GERENCIADA nunca recebe `email`."
          },
          "enrollment_required": {
            "type": "boolean",
            "description": "Conta GERENCIADA sem fator utilizável (TOTP confirmado): a senha bateu, mas\nNENHUMA sessão e NENHUM e-mail saíram (o e-mail do login é a própria caixa —\ncanal circular). O cliente abre a ativação com `enrollment_token`."
          },
          "enrollment_token": {
            "type": "string",
            "description": "Ticket de ativação (JWT ~15 min, aud própria). Não é sessão: só as rotas\n`/v1/auth/ativacao/*` o aceitam. Presente só com `enrollment_required:true`."
          },
          "must_reset": {
            "type": "boolean",
            "description": "Senha temporária: troca obrigatória (também na ativação)."
          }
        }
      },
      "auth_AtivacaoTicketBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ticket"
        ],
        "properties": {
          "ticket": {
            "type": "string",
            "description": "O `enrollment_token` do login."
          }
        }
      },
      "auth_UserIdBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "user_id"
        ],
        "properties": {
          "user_id": {
            "type": "string"
          }
        }
      },
      "auth_UserSenhaBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "user_id",
          "senha"
        ],
        "description": "Corpo das operações que MEXEM num fator (ligar/trocar). A `senha` é o\nfator ≠ do que se altera (piso 17).",
        "properties": {
          "user_id": {
            "type": "string"
          },
          "senha": {
            "type": "string",
            "format": "password"
          }
        }
      },
      "auth_TwoFAResendBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "two_factor_token"
        ],
        "description": "Reenvio do código de e-mail. A identidade vem do ticket, não de um `user_id`.",
        "properties": {
          "two_factor_token": {
            "type": "string",
            "description": "O ticket de desafio devolvido por `POST /v1/auth/login`."
          }
        }
      },
      "auth_TwoFACodeBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "user_id",
          "code"
        ],
        "properties": {
          "user_id": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "TOTP de 6 dígitos."
          }
        }
      },
      "auth_TwoFACheckBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "two_factor_token",
          "code"
        ],
        "description": "Corpo do 2º passo do login. Sem `user_id`: a identidade e a escolha de\nsessão longa vêm do `two_factor_token` assinado, não de um campo cru.",
        "properties": {
          "two_factor_token": {
            "type": "string",
            "description": "O ticket de desafio devolvido por `POST /v1/auth/login`."
          },
          "code": {
            "type": "string",
            "description": "Código de 6 dígitos do canal da conta (app ou e-mail) OU um código de recuperação (XXXX-XXXX-XXXX, uso único; gasto grava `codigo_recuperacao_usado` e avisa)."
          }
        }
      },
      "auth_Discovery": {
        "type": "object",
        "description": "Metadados do provedor OIDC (OIDC Discovery + RFC 8414). Cacheável (`Cache-Control: public, max-age=300`); muda só em deploy/rotação.",
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://auth.asender.net"
            ]
          },
          "authorization_endpoint": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://auth.asender.net/authorize"
            ]
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://auth.asender.net/token"
            ]
          },
          "userinfo_endpoint": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://auth.asender.net/userinfo"
            ]
          },
          "jwks_uri": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://auth.asender.net/.well-known/jwks.json"
            ]
          },
          "end_session_endpoint": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://auth.asender.net/logout"
            ]
          },
          "registration_endpoint": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://auth.asender.net/register"
            ],
            "description": "RFC 7591; só quando OAUTH_DCR_ATIVO=true."
          },
          "client_id_metadata_document_supported": {
            "type": "boolean",
            "description": "CIMD; só quando OAUTH_CIMD_ATIVO=true."
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "code"
              ]
            ]
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "authorization_code",
                "refresh_token"
              ]
            ]
          },
          "subject_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "public"
              ]
            ]
          },
          "id_token_signing_alg_values_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "RS256"
              ]
            ]
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "client_secret_basic",
                "client_secret_post",
                "none"
              ]
            ]
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "S256"
              ]
            ]
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "openid",
                "profile",
                "email",
                "offline_access"
              ]
            ]
          },
          "claims_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "auth_JWKS": {
        "type": "object",
        "description": "Conjunto de chaves públicas RS256 (JWK Set) para validar a assinatura dos tokens.",
        "required": [
          "keys"
        ],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/auth_JWK"
            }
          }
        }
      },
      "auth_JWK": {
        "type": "object",
        "description": "Uma chave pública RSA no formato JWK (RFC 7517).",
        "required": [
          "kty",
          "use",
          "alg",
          "kid",
          "n",
          "e"
        ],
        "properties": {
          "kty": {
            "type": "string",
            "enum": [
              "RSA"
            ]
          },
          "use": {
            "type": "string",
            "enum": [
              "sig"
            ]
          },
          "alg": {
            "type": "string",
            "enum": [
              "RS256"
            ]
          },
          "kid": {
            "type": "string",
            "description": "key id; casa com o header `kid` do JWT para escolher a chave certa numa rotação"
          },
          "n": {
            "type": "string",
            "description": "módulo RSA, base64url sem padding"
          },
          "e": {
            "type": "string",
            "description": "expoente RSA, base64url sem padding"
          }
        }
      },
      "auth_TokenResponse": {
        "type": "object",
        "description": "Resposta de sucesso do `POST /token` (RFC 6749 §5.1 + OIDC). `Cache-Control: no-store`.",
        "required": [
          "access_token",
          "token_type",
          "expires_in"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "JWT RS256 (`tok_use:access`) para chamar as APIs; `aud` = client_id"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "description": "segundos até o access token expirar"
          },
          "id_token": {
            "type": "string",
            "description": "JWT RS256 (`tok_use:id`) que prova QUEM logou; validado no client contra o JWKS"
          },
          "refresh_token": {
            "type": "string",
            "description": "presente só quando o scope pediu `offline_access`; opaco, rotacionado a cada uso"
          },
          "scope": {
            "type": "string",
            "description": "os escopos efetivamente concedidos (inclui sempre `openid`)"
          }
        }
      },
      "auth_ErroOAuth": {
        "type": "object",
        "description": "Envelope de erro OAuth (RFC 6749 §5.2), usado por `/token` e `/userinfo`.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "código do erro",
            "examples": [
              "invalid_grant",
              "invalid_client",
              "invalid_request",
              "unsupported_grant_type",
              "invalid_token",
              "server_error"
            ]
          },
          "error_description": {
            "type": "string",
            "description": "detalhe legível; nunca vaza segredo nem existência de recurso alheio"
          }
        }
      },
      "auth_Introspeccao": {
        "type": "object",
        "description": "Resposta da introspecção (RFC 7662). Inativo traz só `active:false`.",
        "required": [
          "active"
        ],
        "properties": {
          "active": {
            "type": "boolean"
          },
          "scope": {
            "type": "string"
          },
          "client_id": {
            "type": "string"
          },
          "sub": {
            "type": "string",
            "description": "ID interno imutável do usuário"
          },
          "tenant": {
            "type": "string",
            "description": "a conta (acc_…) do consentimento"
          },
          "exp": {
            "type": "integer"
          },
          "iat": {
            "type": "integer"
          },
          "iss": {
            "type": "string"
          },
          "aud": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "jti": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer",
              "refresh_token"
            ]
          }
        }
      },
      "auth_EscopoOAuth": {
        "type": "object",
        "required": [
          "id",
          "rotulo",
          "descricao",
          "leitura"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "leads:read"
            ]
          },
          "rotulo": {
            "type": "string",
            "description": "pt-BR, para a tela de consentimento"
          },
          "descricao": {
            "type": "string"
          },
          "leitura": {
            "type": "boolean",
            "description": "true nos escopos `*:read`"
          }
        }
      },
      "auth_ConsentimentoView": {
        "type": "object",
        "required": [
          "client",
          "escopos",
          "contas"
        ],
        "properties": {
          "client": {
            "type": "object",
            "properties": {
              "nome": {
                "type": "string"
              },
              "site_url": {
                "type": "string"
              },
              "logo_url": {
                "type": "string"
              },
              "verificado": {
                "type": "boolean",
                "description": "selo Verificado pela Asender"
              },
              "estado": {
                "type": "string",
                "enum": [
                  "desenvolvimento",
                  "em_revisao",
                  "publicado",
                  "recusado",
                  "suspenso"
                ]
              },
              "origem": {
                "type": "string",
                "enum": [
                  "ops",
                  "portal",
                  "cimd",
                  "dcr"
                ]
              },
              "dominios_redirect": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "hosts únicos (sem porta) dos redirect_uris"
              }
            }
          },
          "escopos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "rotulo": {
                  "type": "string"
                },
                "descricao": {
                  "type": "string"
                }
              }
            }
          },
          "contas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "acc_…"
                },
                "nome": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "auth_AppPortal": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string"
          },
          "nome": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "confidential",
              "public"
            ]
          },
          "site_url": {
            "type": "string"
          },
          "logo_url": {
            "type": "string"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "escopos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "declarados"
          },
          "escopos_aprovados": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "null = ainda sem revisão"
          },
          "estado": {
            "type": "string",
            "enum": [
              "desenvolvimento",
              "em_revisao",
              "publicado",
              "recusado",
              "suspenso"
            ]
          },
          "origem": {
            "type": "string",
            "enum": [
              "ops",
              "portal",
              "cimd",
              "dcr"
            ]
          },
          "verificado": {
            "type": "boolean"
          },
          "motivo_estado": {
            "type": "string",
            "description": "motivo da última recusa ou suspensão"
          },
          "conta": {
            "type": "string",
            "description": "acc_… dona"
          },
          "revisao_pendente": {
            "type": "boolean"
          },
          "criado_por": {
            "type": "string"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "auth_AppPortalNovo": {
        "type": "object",
        "required": [
          "conta",
          "nome",
          "redirect_uris",
          "escopos"
        ],
        "properties": {
          "conta": {
            "type": "string",
            "description": "acc_… (owner/admin)"
          },
          "nome": {
            "type": "string",
            "maxLength": 100
          },
          "site_url": {
            "type": "string",
            "format": "uri"
          },
          "logo_url": {
            "type": "string",
            "format": "uri"
          },
          "redirect_uris": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string"
            }
          },
          "escopos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "type": {
            "type": "string",
            "enum": [
              "confidential",
              "public"
            ],
            "default": "confidential"
          }
        }
      },
      "auth_AppPortalAjuste": {
        "type": "object",
        "properties": {
          "nome": {
            "type": "string"
          },
          "site_url": {
            "type": "string"
          },
          "logo_url": {
            "type": "string"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "escopos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "auth_TestadorApp": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string",
            "description": "id público usr_…"
          },
          "nome": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "convidado_em": {
            "type": "string"
          }
        }
      },
      "auth_RevisaoApp": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "pedido_por": {
            "type": "string",
            "description": "só na visão da equipe"
          },
          "escopos_pedidos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "decisao": {
            "type": "string",
            "description": "vazio = pendente; aprovado, recusado, suspenso, reativado, verificado, nao_verificado, publicado_ops"
          },
          "revisor": {
            "type": "string",
            "description": "só na visão da equipe"
          },
          "motivo": {
            "type": "string"
          },
          "escopos_aprovados": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            }
          },
          "decidido_em": {
            "type": "string",
            "nullable": true
          },
          "criado_em": {
            "type": "string"
          }
        }
      },
      "auth_ItemFilaRevisao": {
        "type": "object",
        "properties": {
          "revisao_id": {
            "type": "string"
          },
          "app": {
            "$ref": "#/components/schemas/auth_AppPortal"
          },
          "pedido_por": {
            "type": "string"
          },
          "escopos_pedidos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pedido_em": {
            "type": "string"
          }
        }
      },
      "auth_BloqueioDominio": {
        "type": "object",
        "required": [
          "dominio",
          "motivo",
          "bloqueado_por"
        ],
        "properties": {
          "dominio": {
            "type": "string"
          },
          "motivo": {
            "type": "string"
          },
          "bloqueado_por": {
            "type": "string"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "auth_RegistroClientePedido": {
        "type": "object",
        "required": [
          "redirect_uris"
        ],
        "description": "Metadados do RFC 7591 aceitos; os demais campos são ignorados.",
        "properties": {
          "client_name": {
            "type": "string",
            "maxLength": 100
          },
          "client_uri": {
            "type": "string",
            "format": "uri"
          },
          "logo_uri": {
            "type": "string",
            "format": "uri"
          },
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ]
          },
          "scope": {
            "type": "string",
            "description": "Separado por espaço; só identidade e leitura são mantidos."
          }
        }
      },
      "auth_RegistroClienteResposta": {
        "type": "object",
        "required": [
          "client_id",
          "client_id_issued_at",
          "redirect_uris",
          "grant_types",
          "response_types",
          "token_endpoint_auth_method",
          "scope"
        ],
        "description": "Metadados EFETIVOS. Nunca traz `client_secret` nem `registration_access_token`.",
        "properties": {
          "client_id": {
            "type": "string",
            "examples": [
              "dcr_0123456789abcdef0123456789abcdef"
            ]
          },
          "client_id_issued_at": {
            "type": "integer",
            "description": "Unix, segundos."
          },
          "client_name": {
            "type": "string"
          },
          "client_uri": {
            "type": "string"
          },
          "logo_uri": {
            "type": "string"
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "none"
            ]
          },
          "scope": {
            "type": "string"
          }
        }
      },
      "auth_MotivoRevisao": {
        "type": "object",
        "required": [
          "motivo"
        ],
        "properties": {
          "motivo": {
            "type": "string",
            "minLength": 5,
            "maxLength": 500
          }
        }
      },
      "auth_ConsentimentoDecisao": {
        "type": "object",
        "required": [
          "decisao"
        ],
        "properties": {
          "conta": {
            "type": "string",
            "description": "acc_… escolhida; obrigatória em `aprovar`"
          },
          "decisao": {
            "type": "string",
            "enum": [
              "aprovar",
              "negar"
            ]
          }
        }
      },
      "auth_AppConectado": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "id do grant (use em DELETE)"
          },
          "nome": {
            "type": "string"
          },
          "site_url": {
            "type": "string"
          },
          "logo_url": {
            "type": "string"
          },
          "conta": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "nome": {
                "type": "string"
              }
            }
          },
          "escopos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "rotulo": {
                  "type": "string"
                },
                "descricao": {
                  "type": "string"
                }
              }
            }
          },
          "conectado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "auth_OAuthClient": {
        "type": "object",
        "description": "Um app de terceiro. Nunca carrega o segredo nem o hash.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "confidential",
              "public"
            ]
          },
          "client_id": {
            "type": "string",
            "examples": [
              "app_0123456789abcdef"
            ]
          },
          "nome": {
            "type": "string"
          },
          "site_url": {
            "type": "string"
          },
          "logo_url": {
            "type": "string"
          },
          "redirects": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "escopos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "refresh_ttl_s": {
            "type": "integer",
            "description": "0 = padrão do AS"
          },
          "ativo": {
            "type": "boolean"
          },
          "dono_tenant": {
            "type": "string"
          },
          "criado_por": {
            "type": "string"
          },
          "criado_em": {
            "type": "string"
          }
        }
      },
      "auth_OAuthClientNovo": {
        "type": "object",
        "required": [
          "nome",
          "redirects",
          "escopos"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "confidential",
              "public"
            ],
            "default": "confidential",
            "description": "público = sem segredo (apps nativos/CLI/MCP)"
          },
          "nome": {
            "type": "string",
            "maxLength": 100
          },
          "site_url": {
            "type": "string",
            "format": "uri"
          },
          "logo_url": {
            "type": "string",
            "format": "uri"
          },
          "redirects": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "escopos": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            }
          },
          "refresh_ttl_s": {
            "type": "integer",
            "minimum": 3600,
            "maximum": 31536000
          },
          "dono_tenant": {
            "type": "string"
          },
          "motivo": {
            "type": "string"
          }
        }
      },
      "auth_OAuthClientAjuste": {
        "type": "object",
        "properties": {
          "nome": {
            "type": "string"
          },
          "site_url": {
            "type": "string"
          },
          "logo_url": {
            "type": "string"
          },
          "redirects": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "escopos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "refresh_ttl_s": {
            "type": "integer"
          },
          "ativo": {
            "type": "boolean"
          },
          "motivo": {
            "type": "string"
          }
        }
      },
      "auth_Ok": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeConta": {
        "type": "object",
        "required": [
          "user",
          "fatores"
        ],
        "properties": {
          "user": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "examples": [
                  "usr_demo_owner"
                ]
              },
              "name": {
                "type": "string"
              },
              "email_primario": {
                "type": "string",
                "format": "email"
              },
              "criado_em": {
                "type": "string",
                "format": "date-time"
              },
              "password_changed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "must_reset": {
                "type": "boolean"
              }
            }
          },
          "fatores": {
            "type": "object",
            "properties": {
              "senha": {
                "type": "boolean"
              },
              "email_otp": {
                "type": "boolean"
              },
              "totp": {
                "type": "boolean"
              },
              "codigos_restantes": {
                "type": "integer"
              },
              "passkeys": {
                "type": "integer"
              }
            }
          },
          "reauth": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/auth_MeReauthEstado"
              },
              {
                "type": "null"
              }
            ]
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MePerfilBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "auth_MeMetodo": {
        "type": "string",
        "enum": [
          "email_otp",
          "totp",
          "senha",
          "codigo_recuperacao",
          "passkey"
        ]
      },
      "auth_MeReauthStartBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "metodo"
        ],
        "properties": {
          "metodo": {
            "$ref": "#/components/schemas/auth_MeMetodo"
          },
          "email_id": {
            "type": "string",
            "format": "uuid",
            "description": "Só `email_otp`: o e-mail VERIFICADO que recebe o código (ausente = padrão)."
          },
          "origem": {
            "type": "string",
            "format": "uri",
            "description": "Só `passkey`: a origem do auth_front em que a cerimônia roda (allowlist).",
            "examples": [
              "https://auth.asender.net"
            ]
          }
        }
      },
      "auth_MeDestino": {
        "type": "object",
        "description": "Destino possível do e-mail OTP da reauth. O endereço em claro nunca sai.",
        "required": [
          "email_id",
          "mascarado",
          "primario"
        ],
        "properties": {
          "email_id": {
            "type": "string",
            "format": "uuid"
          },
          "mascarado": {
            "type": "string",
            "examples": [
              "m***@test.asender.com.br"
            ]
          },
          "primario": {
            "type": "boolean"
          }
        }
      },
      "auth_MeReauthStartResp": {
        "type": "object",
        "properties": {
          "metodo": {
            "$ref": "#/components/schemas/auth_MeMetodo"
          },
          "destinos": {
            "type": "array",
            "description": "Só `email_otp`. Primário primeiro; o 1º é o padrão.",
            "items": {
              "$ref": "#/components/schemas/auth_MeDestino"
            }
          },
          "enviado_para": {
            "type": "string",
            "description": "Só `email_otp`: o mascarado de quem recebeu."
          },
          "desafio_id": {
            "type": "string",
            "format": "uuid",
            "description": "Só `passkey`: volta no confirm (uso único, 5 min, preso a esta sessão)."
          },
          "opcoes": {
            "$ref": "#/components/schemas/auth_WebAuthnOpcoes"
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeReauthRequiredDetails": {
        "type": "object",
        "description": "`Error.Details` do 403 ReauthRequired.",
        "properties": {
          "metodos_aceitos": {
            "type": "array",
            "description": "Os métodos da política que a conta PODE usar agora (vazio = nenhum: a tela explica o que fazer).",
            "items": {
              "$ref": "#/components/schemas/auth_MeMetodo"
            }
          },
          "metodos": {
            "type": "object",
            "description": "P5: TODOS os métodos da política da rota, cada um com `disponivel` e, se não, o `motivo`.",
            "additionalProperties": {
              "$ref": "#/components/schemas/auth_MeMetodoEstado"
            },
            "examples": [
              {
                "senha": {
                  "disponivel": true
                },
                "email_otp": {
                  "disponivel": false,
                  "motivo": "EmailNaoVerificado"
                }
              }
            ]
          },
          "destinos": {
            "type": "array",
            "description": "Só quando `email_otp` está entre os aceitos.",
            "items": {
              "$ref": "#/components/schemas/auth_MeDestino"
            }
          }
        }
      },
      "auth_MeMetodoEstado": {
        "type": "object",
        "required": [
          "disponivel"
        ],
        "properties": {
          "disponivel": {
            "type": "boolean"
          },
          "motivo": {
            "type": "string",
            "enum": [
              "EmailNaoVerificado",
              "TOTPNaoAtivado",
              "SemCodigos",
              "SemPasskey",
              "SemSenha",
              "NaoSuportado"
            ],
            "description": "Só quando indisponível. `EmailNaoVerificado`: nenhum e-mail verificado — a tela manda verificar (reenviar não pede reauth)."
          }
        }
      },
      "auth_WebAuthnOpcoes": {
        "type": "object",
        "description": "As opções da cerimônia WebAuthn como a go-webauthn as serializa: `{publicKey:{…}}`, campos binários (challenge, user.id, ids de credencial) em base64url.",
        "properties": {
          "publicKey": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "auth_WebAuthnCredencial": {
        "type": "object",
        "description": "A PublicKeyCredential em JSON (formato `toJSON` da especificação): id, rawId, type, response{clientDataJSON, attestationObject|authenticatorData+signature+userHandle}, clientExtensionResults.",
        "required": [
          "id",
          "rawId",
          "type",
          "response"
        ],
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "rawId": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "public-key"
            ]
          },
          "response": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "auth_MePasskey": {
        "type": "object",
        "description": "Uma chave de acesso. Nunca sai a chave pública nem o credential id.",
        "required": [
          "id",
          "nome",
          "criado_em",
          "sincronizada",
          "rp_id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string",
            "maxLength": 64
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "usado_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sincronizada": {
            "type": "boolean",
            "description": "Credencial de backup (iCloud/Google): BE=1."
          },
          "rp_id": {
            "type": "string",
            "examples": [
              "auth.asender.net"
            ]
          }
        }
      },
      "auth_MePasskeys": {
        "type": "object",
        "properties": {
          "passkeys": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/auth_MePasskey"
            }
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MePasskeyRegistroStartBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "origem"
        ],
        "properties": {
          "origem": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://auth.asender.net"
            ]
          }
        }
      },
      "auth_MePasskeyDesafio": {
        "type": "object",
        "properties": {
          "desafio_id": {
            "type": "string",
            "format": "uuid"
          },
          "opcoes": {
            "$ref": "#/components/schemas/auth_WebAuthnOpcoes"
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MePasskeyRegistroFinishBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "desafio_id",
          "credencial"
        ],
        "properties": {
          "desafio_id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string",
            "maxLength": 64,
            "description": "Vazio = \"Chave de acesso\"."
          },
          "credencial": {
            "$ref": "#/components/schemas/auth_WebAuthnCredencial"
          }
        }
      },
      "auth_MePasskeyResp": {
        "type": "object",
        "properties": {
          "passkey": {
            "$ref": "#/components/schemas/auth_MePasskey"
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MePasskeyRenomearBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "nome"
        ],
        "properties": {
          "nome": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          }
        }
      },
      "auth_PasskeyLoginFinishBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "desafio_id",
          "credencial"
        ],
        "properties": {
          "desafio_id": {
            "type": "string",
            "format": "uuid"
          },
          "credencial": {
            "$ref": "#/components/schemas/auth_WebAuthnCredencial"
          },
          "lembrar": {
            "type": "boolean",
            "description": "Sessão longa (90d) em vez de 7d."
          }
        }
      },
      "auth_MeReauthConfirmBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "metodo"
        ],
        "properties": {
          "metodo": {
            "$ref": "#/components/schemas/auth_MeMetodo"
          },
          "codigo": {
            "type": "string",
            "description": "email_otp, totp ou codigo_recuperacao."
          },
          "senha": {
            "type": "string",
            "writeOnly": true,
            "description": "Só para metodo=senha."
          },
          "desafio_id": {
            "type": "string",
            "format": "uuid",
            "description": "Só para metodo=passkey: o do start."
          },
          "credencial": {
            "$ref": "#/components/schemas/auth_WebAuthnCredencial"
          }
        }
      },
      "auth_MeReauthEstado": {
        "type": "object",
        "properties": {
          "valida_ate": {
            "type": "string",
            "format": "date-time"
          },
          "metodo": {
            "$ref": "#/components/schemas/auth_MeMetodo"
          }
        }
      },
      "auth_MeSenhaBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "nova"
        ],
        "properties": {
          "nova": {
            "type": "string",
            "writeOnly": true
          },
          "encerrar_outras": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "auth_MeSenhaResp": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "sessoes_encerradas": {
            "type": "integer"
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeEmailBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "auth_MeEmails": {
        "type": "object",
        "properties": {
          "emails": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "email": {
                  "type": "string",
                  "format": "email"
                },
                "verified": {
                  "type": "boolean"
                },
                "primary": {
                  "type": "boolean"
                },
                "pendente_expira_em": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "estado": {
                  "type": "string",
                  "enum": [
                    "verificado",
                    "pendente",
                    "em_uso_por_outra_conta"
                  ]
                }
              }
            }
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeTOTPSetup": {
        "type": "object",
        "properties": {
          "otpauth_url": {
            "type": "string"
          },
          "secret": {
            "type": "string"
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeCodigoBody": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "codigo"
        ],
        "properties": {
          "codigo": {
            "type": "string"
          }
        }
      },
      "auth_MeCodigoRecuperacao": {
        "type": "string",
        "description": "12 símbolos de um alfabeto sem ambíguos (sem 0/O, 1/I/L, U), em 3 grupos; CSPRNG, ~58,9 bits. Aceito também sem hífen/minúsculo.",
        "pattern": "^[2-9A-HJKMNP-TV-Z]{4}-[2-9A-HJKMNP-TV-Z]{4}-[2-9A-HJKMNP-TV-Z]{4}$",
        "examples": [
          "K7QF-2MZD-9RTA"
        ]
      },
      "auth_MeTOTPConfirmado": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "codigos_recuperacao": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/auth_MeCodigoRecuperacao"
            },
            "maxItems": 10,
            "description": "10 na 1ª ativação (conta sem códigos vivos); vazio quando já havia. Mostrados UMA vez."
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeRemocaoAgendada": {
        "type": "object",
        "required": [
          "id",
          "fator",
          "agendado_para"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fator": {
            "type": "string",
            "enum": [
              "totp",
              "passkey"
            ]
          },
          "agendado_para": {
            "type": "string",
            "format": "date-time"
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeCodigos": {
        "type": "object",
        "properties": {
          "codigos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/auth_MeCodigoRecuperacao"
            },
            "minItems": 10,
            "maxItems": 10
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeFatores": {
        "type": "object",
        "required": [
          "fatores",
          "remocoes_pendentes"
        ],
        "properties": {
          "fatores": {
            "type": "object",
            "description": "Fatores de login (senha, e-mail OTP no primário verificado, app, passkeys). Códigos de recuperação não contam no mínimo de 2.",
            "properties": {
              "senha": {
                "type": "boolean"
              },
              "email_otp": {
                "type": "boolean"
              },
              "totp": {
                "type": "boolean"
              },
              "codigos_restantes": {
                "type": "integer",
                "minimum": 0
              },
              "passkeys": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "remocoes_pendentes": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "fator",
                "executar_em"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "fator": {
                  "type": "string",
                  "enum": [
                    "totp",
                    "passkey"
                  ]
                },
                "executar_em": {
                  "type": "string",
                  "format": "date-time"
                },
                "alvo_id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Só `passkey`: a chave que sai."
                }
              }
            }
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeSessoes": {
        "type": "object",
        "properties": {
          "sessoes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "user_agent": {
                  "type": "string"
                },
                "ip": {
                  "type": "string"
                },
                "criada_em": {
                  "type": "string",
                  "format": "date-time"
                },
                "usada_em": {
                  "type": "string",
                  "format": "date-time"
                },
                "expira_em": {
                  "type": "string",
                  "format": "date-time"
                },
                "atual": {
                  "type": "boolean"
                }
              }
            }
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeSenhasApp": {
        "type": "object",
        "properties": {
          "senhas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/auth_MeSenhaApp"
            }
          },
          "por_caixa": {
            "type": "boolean",
            "description": "Capacidade: true quando a criação de senha presa a UMA caixa está ligada (APP_PASSWORD_POR_CAIXA). Falso: a tela esconde o seletor de alcance."
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeSenhaApp": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[a-z2-7]{8}$",
            "description": "O id curto (parte da credencial); não é segredo."
          },
          "rotulo": {
            "type": "string",
            "maxLength": 60
          },
          "escopos": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "mail:imap",
                "mail:smtp"
              ]
            }
          },
          "criada_em": {
            "type": "string",
            "format": "date-time"
          },
          "expira_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ultimo_uso_em": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ultimo_ip": {
            "type": "string"
          },
          "caixa": {
            "type": [
              "string",
              "null"
            ],
            "description": "Endereço (minúsculo) a que a senha está presa; null = todas as caixas com grant."
          }
        }
      },
      "auth_MeSenhaAppBody": {
        "type": "object",
        "required": [
          "rotulo",
          "escopos"
        ],
        "additionalProperties": false,
        "properties": {
          "rotulo": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60
          },
          "escopos": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "enum": [
                "mail:imap",
                "mail:smtp"
              ]
            }
          },
          "caixa": {
            "type": "string",
            "maxLength": 254,
            "description": "Opcional. Prende a senha a UMA caixa (e-mail; normalizado para minúsculo). Formato inválido: 400 ValidationError. Recurso desligado (APP_PASSWORD_POR_CAIXA): 400 RecursoDesligado."
          }
        }
      },
      "auth_MeSenhaAppEmitida": {
        "allOf": [
          {
            "$ref": "#/components/schemas/auth_MeSenhaApp"
          },
          {
            "type": "object",
            "properties": {
              "segredo": {
                "type": "string",
                "pattern": "^asp_[a-z2-7]{8}_[a-z2-7]{32}$",
                "description": "A credencial inteira. Mostrada UMA vez."
              },
              "RequestId": {
                "type": "string"
              }
            }
          }
        ]
      },
      "auth_SenhaAppValidarBody": {
        "type": "object",
        "required": [
          "segredo",
          "escopo",
          "ip"
        ],
        "additionalProperties": false,
        "properties": {
          "segredo": {
            "type": "string",
            "description": "A credencial digitada no cliente de e-mail."
          },
          "escopo": {
            "type": "string",
            "enum": [
              "mail:imap",
              "mail:smtp"
            ]
          },
          "ip": {
            "type": "string",
            "description": "IP do cliente de e-mail (para o freio e a auditoria)."
          }
        }
      },
      "auth_SenhaAppValidada": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string",
            "pattern": "^usr_[0-9a-f]{16}$"
          },
          "app_id": {
            "type": "string",
            "pattern": "^[a-z2-7]{8}$"
          },
          "rotulo": {
            "type": "string"
          },
          "caixa_endereco": {
            "type": "string",
            "description": "Presente SÓ quando a senha é presa a uma caixa: o gateway deve recusar o login se o username for outro endereço. Ausente = ampla."
          }
        }
      },
      "auth_MeEncerradas": {
        "type": "object",
        "properties": {
          "encerradas": {
            "type": "integer"
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_MeEventos": {
        "type": "object",
        "properties": {
          "eventos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "tipo": {
                  "type": "string",
                  "enum": [
                    "senha_trocada",
                    "email_adicionado",
                    "email_verificado",
                    "email_removido",
                    "primario_trocado",
                    "totp_ativado",
                    "segundo_fator_ativado_primeiro_acesso",
                    "totp_remocao_agendada",
                    "totp_remocao_cancelada",
                    "totp_desativado",
                    "codigos_gerados",
                    "codigo_recuperacao_usado",
                    "sessao_revogada",
                    "sessoes_encerradas",
                    "reauth_ok",
                    "reauth_falhou",
                    "nome_alterado",
                    "senha_app_criada",
                    "senha_app_revogada",
                    "senha_app_falha_agregada",
                    "senha_app_primeiro_uso_por_ip"
                  ]
                },
                "criado_em": {
                  "type": "string",
                  "format": "date-time"
                },
                "ip": {
                  "type": "string"
                },
                "user_agent": {
                  "type": "string"
                },
                "detalhe": {
                  "type": "object"
                }
              }
            }
          },
          "proximo": {
            "type": [
              "string",
              "null"
            ]
          },
          "RequestId": {
            "type": "string"
          }
        }
      },
      "auth_LixeiraTipoRecurso": {
        "type": "string",
        "pattern": "^[a-z0-9_]{1,40}$",
        "description": "Tipo do registro de tipos do servico (ex. mensagem, caixa, pixeldominio)."
      },
      "auth_LixeiraEstagio": {
        "type": "string",
        "enum": [
          "usuario",
          "admin",
          "retido"
        ]
      },
      "auth_LixeiraInstanteUtc": {
        "type": "string",
        "format": "date-time",
        "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{6}Z$",
        "description": "UTC com exatamente 6 casas (a ordenacao lexical do BFF depende disso)."
      },
      "auth_LixeiraItemLixeira": {
        "type": "object",
        "required": [
          "id",
          "tipo",
          "nome",
          "estagio",
          "apagado_em"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "tipo": {
            "$ref": "#/components/schemas/auth_LixeiraTipoRecurso"
          },
          "nome": {
            "type": "string",
            "maxLength": 120,
            "description": "Rotulo SEGURO para reconhecer o item. Nunca corpo, conteudo, segredo ou PII alem do necessario."
          },
          "estagio": {
            "$ref": "#/components/schemas/auth_LixeiraEstagio"
          },
          "apagado_em": {
            "$ref": "#/components/schemas/auth_LixeiraInstanteUtc"
          },
          "apagado_por": {
            "type": "string",
            "description": "Id imutavel do usuario/servico; nunca e-mail"
          },
          "apagado_por_nome": {
            "type": "string",
            "maxLength": 120
          },
          "apagado_motivo": {
            "type": "string",
            "maxLength": 500
          },
          "motivo": {
            "type": "string",
            "deprecated": true,
            "description": "Alias legado de `apagado_motivo` (o BFF le `motivo`). Mesmo valor."
          },
          "expira_usuario_em": {
            "$ref": "#/components/schemas/auth_LixeiraInstanteUtc"
          },
          "dias_restantes": {
            "type": "integer",
            "minimum": 0,
            "description": "Dias ate o fim do estagio corrente"
          },
          "movido_admin_em": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/auth_LixeiraInstanteUtc"
              },
              {
                "type": "null"
              }
            ]
          },
          "expira_admin_em": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/auth_LixeiraInstanteUtc"
              },
              {
                "type": "null"
              }
            ]
          },
          "retido_ate": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/auth_LixeiraInstanteUtc"
              },
              {
                "type": "null"
              }
            ]
          },
          "pai_apagado": {
            "type": "boolean",
            "description": "true se o pai ainda esta apagado (restaurar exige restaurar o pai primeiro)."
          },
          "pai": {
            "type": "object",
            "properties": {
              "tipo": {
                "$ref": "#/components/schemas/auth_LixeiraTipoRecurso"
              },
              "id": {
                "type": "string"
              }
            }
          },
          "lote_id": {
            "type": "string",
            "description": "Itens apagados juntos (cascata logica) restauram em bloco"
          },
          "tamanho_bytes": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "auth_LixeiraPaginaLixeira": {
        "type": "object",
        "required": [
          "itens",
          "cursor"
        ],
        "properties": {
          "itens": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/auth_LixeiraItemLixeira"
            }
          },
          "cursor": {
            "type": "string",
            "description": "Vazio = fim"
          },
          "proximo": {
            "type": "string",
            "deprecated": true,
            "description": "Alias legado de `cursor`"
          }
        }
      },
      "auth_LixeiraRestaurarInterno": {
        "type": "object",
        "required": [
          "por"
        ],
        "properties": {
          "por": {
            "type": "string",
            "maxLength": 128
          },
          "motivo": {
            "type": "string",
            "minLength": 10,
            "maxLength": 500,
            "description": "Obrigatorio em estagio admin/retido"
          }
        }
      },
      "auth_LixeiraAnteciparInterno": {
        "type": "object",
        "required": [
          "motivo",
          "confirmacao"
        ],
        "properties": {
          "motivo": {
            "type": "string",
            "minLength": 10,
            "maxLength": 500
          },
          "confirmacao": {
            "type": "string",
            "description": "Deve ser igual ao `id` do item"
          }
        }
      },
      "auth_LixeiraReterInterno": {
        "type": "object",
        "required": [
          "ate",
          "motivo"
        ],
        "properties": {
          "ate": {
            "$ref": "#/components/schemas/auth_LixeiraInstanteUtc"
          },
          "motivo": {
            "type": "string",
            "minLength": 10,
            "maxLength": 500
          }
        }
      },
      "auth_LixeiraLiberarInterno": {
        "type": "object",
        "required": [
          "motivo"
        ],
        "properties": {
          "motivo": {
            "type": "string",
            "minLength": 10,
            "maxLength": 500
          }
        }
      },
      "auth_LixeiraPurgaAgendada": {
        "type": "object",
        "required": [
          "estado",
          "trilha_id"
        ],
        "properties": {
          "estado": {
            "type": "string",
            "enum": [
              "agendada",
              "na_fila_do_purgador"
            ]
          },
          "trilha_id": {
            "type": "string"
          }
        }
      },
      "auth_LixeiraEstatisticas": {
        "type": "object",
        "required": [
          "por_estagio",
          "vencendo_7d",
          "retidos",
          "blob_pendente"
        ],
        "properties": {
          "por_estagio": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "estagio",
                "tipo",
                "total"
              ],
              "properties": {
                "estagio": {
                  "$ref": "#/components/schemas/auth_LixeiraEstagio"
                },
                "tipo": {
                  "$ref": "#/components/schemas/auth_LixeiraTipoRecurso"
                },
                "total": {
                  "type": "integer",
                  "minimum": 0
                },
                "bytes": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          },
          "vencendo_7d": {
            "type": "integer",
            "minimum": 0
          },
          "retidos": {
            "type": "integer",
            "minimum": 0
          },
          "blob_pendente": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "auth_LixeiraProblem": {
        "type": "object",
        "description": "RFC 9457",
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "trace_id": {
            "type": "string"
          },
          "motivo": {
            "type": "string",
            "description": "Slug (alias do `motivo` legado)"
          },
          "pai": {
            "type": "object",
            "properties": {
              "tipo": {
                "$ref": "#/components/schemas/auth_LixeiraTipoRecurso"
              },
              "id": {
                "type": "string"
              }
            }
          },
          "filhos": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "properties": {
                "tipo": {
                  "$ref": "#/components/schemas/auth_LixeiraTipoRecurso"
                },
                "id": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "crm_ResumoDeFerramenta": {
        "type": "object",
        "description": "Contrato comum de resumo de ferramenta (plano 2026-10-02) — o mesmo formato em toda ferramenta.",
        "required": [
          "ferramenta",
          "periodo",
          "indicadores",
          "series"
        ],
        "properties": {
          "ferramenta": {
            "type": "string",
            "description": "slug da ferramenta (crm, mail, pages, atendimento, …)"
          },
          "periodo": {
            "type": "object",
            "required": [
              "de",
              "ate",
              "chave"
            ],
            "properties": {
              "de": {
                "type": "string",
                "format": "date",
                "description": "primeiro dia da janela (inclusivo)"
              },
              "ate": {
                "type": "string",
                "format": "date",
                "description": "último dia da janela (inclusivo)"
              },
              "chave": {
                "type": "string",
                "enum": [
                  "7d",
                  "30d",
                  "90d"
                ]
              }
            }
          },
          "indicadores": {
            "type": "array",
            "minItems": 1,
            "maxItems": 6,
            "items": {
              "$ref": "#/components/schemas/crm_ResumoIndicador"
            }
          },
          "series": {
            "type": "array",
            "maxItems": 2,
            "items": {
              "$ref": "#/components/schemas/crm_ResumoSerie"
            }
          }
        }
      },
      "crm_ResumoIndicador": {
        "type": "object",
        "required": [
          "chave",
          "rotulo",
          "valor",
          "anterior",
          "formato",
          "link"
        ],
        "properties": {
          "chave": {
            "type": "string",
            "description": "id estável do indicador"
          },
          "rotulo": {
            "type": "string",
            "description": "rótulo pt-BR (o front pode traduzir pela chave)"
          },
          "valor": {
            "type": "number"
          },
          "anterior": {
            "type": [
              "number",
              "null"
            ],
            "description": "mesmo indicador na janela anterior de mesmo tamanho; null = sem comparação"
          },
          "formato": {
            "type": "string",
            "enum": [
              "numero",
              "percentual",
              "moeda",
              "duracao"
            ],
            "description": "moeda em centavos BRL; duracao em segundos; percentual 0–100"
          },
          "link": {
            "type": "string",
            "description": "caminho relativo dentro da ferramenta, já com a conta"
          }
        }
      },
      "crm_ResumoSerie": {
        "type": "object",
        "required": [
          "chave",
          "rotulo",
          "tipo",
          "pontos"
        ],
        "properties": {
          "chave": {
            "type": "string"
          },
          "rotulo": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "linha",
              "barra"
            ]
          },
          "pontos": {
            "type": "array",
            "description": "um ponto por dia da janela (dia sem evento vem com 0)",
            "items": {
              "type": "object",
              "required": [
                "data",
                "valor"
              ],
              "properties": {
                "data": {
                  "type": "string",
                  "format": "date"
                },
                "valor": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "crm_ContagemPorChave": {
        "type": "object",
        "properties": {
          "chave": {
            "type": "string"
          },
          "contagem": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "crm_Erro": {
        "type": "object",
        "description": "Envelope único de erro da CRM API — um campo só.",
        "required": [
          "erro"
        ],
        "properties": {
          "erro": {
            "type": "string",
            "description": "mensagem legível; nunca revela schema nem existência de recurso alheio"
          }
        }
      },
      "crm_TipoDaLixeira": {
        "type": "string",
        "description": "Allowlist de tipos restauráveis pelo usuário. `identidade` é o vínculo desvinculado de um contato (telefone, e-mail, Telegram, LID do WhatsApp): o `nome` é o valor MASCARADO; restaurar dá 409 `conflito_unicidade` se outra identidade ativa da conta já tem o valor e 409 `pai_apagado` (pai `contato`) se o contato dono está apagado.",
        "enum": [
          "contato",
          "cliente",
          "empresa",
          "projeto",
          "coluna",
          "tarefa",
          "segmento",
          "destino",
          "alerta",
          "identidade"
        ]
      },
      "crm_ItemDaLixeira": {
        "type": "object",
        "required": [
          "tipo",
          "id",
          "nome",
          "apagado_em"
        ],
        "properties": {
          "tipo": {
            "$ref": "#/components/schemas/crm_TipoDaLixeira"
          },
          "id": {
            "type": "string",
            "description": "id público do item"
          },
          "nome": {
            "type": "string",
            "description": "nome ou título para reconhecer o item"
          },
          "apagado_em": {
            "type": "string",
            "format": "date-time"
          },
          "apagado_por": {
            "type": "string",
            "description": "id do usuário que apagou (vazio se foi o sistema); o front resolve o nome"
          },
          "motivo": {
            "type": "string",
            "description": "motivo informado ao apagar (contato)"
          }
        }
      },
      "crm_LixeiraPagina": {
        "type": "object",
        "required": [
          "itens",
          "cursor"
        ],
        "properties": {
          "itens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_ItemDaLixeira"
            }
          },
          "cursor": {
            "type": "string",
            "description": "cursor da próxima página; vazio = fim"
          }
        }
      },
      "crm_RestauroRecusado": {
        "type": "object",
        "required": [
          "erro",
          "motivo"
        ],
        "properties": {
          "erro": {
            "type": "string"
          },
          "motivo": {
            "type": "string",
            "enum": [
              "conflito_unicidade",
              "pai_apagado"
            ]
          },
          "pai": {
            "type": "string",
            "enum": [
              "projeto",
              "coluna",
              "cliente",
              "empresa",
              "contato"
            ],
            "description": "só em pai_apagado: o que precisa ser restaurado primeiro"
          }
        }
      },
      "crm_Passo": {
        "type": "object",
        "description": "Um degrau da jornada.",
        "required": [
          "numero",
          "tipo"
        ],
        "properties": {
          "numero": {
            "type": "integer",
            "description": "posição, começando em 1, sem buraco"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "esperar",
              "agir",
              "condicao"
            ]
          },
          "espera": {
            "type": "integer",
            "format": "int64",
            "description": "duração em nanossegundos (só em `esperar`); 1 dia = 86400000000000"
          },
          "acao": {
            "type": "string",
            "enum": [
              "email",
              "push",
              "webhook",
              "mudar_estagio",
              "entrar_lista",
              "enviar_conversao",
              "responder_conversa",
              "encaminhar_fila"
            ],
            "description": "só em `agir`; `enviar_conversao` (ADR-0120) pede parametros instalacao_id, evento, valor?, moeda?"
          },
          "parametros": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "argumentos da ação. `enviar_conversao` (ADR-0120): o operador envia `instalacao_id`, `evento`, `valor?`, `moeda?`; `instalacao_app` é INTERNO (gerido pelo servidor, opcional na entrada): o servidor o grava ao salvar, com o slug confirmado por Integrações, e sobrescreve o que o cliente mandar. Valor fora de `meta_capi|google_ads|tiktok_events` = 422.",
            "properties": {
              "instalacao_app": {
                "type": "string",
                "enum": [
                  "meta_capi",
                  "google_ads",
                  "tiktok_events"
                ],
                "description": "server-managed: não é campo do construtor; o cliente pode omitir"
              }
            }
          },
          "condicao": {
            "type": "string",
            "description": "condição do passo `condicao`. Hoje o CRM só julga a família `email.*` (W3): `email.abriu`, `email.nao_abriu`, `email.clicou`, `email.nao_clicou`, com opções `;passo=N` (passo de e-mail alvo; padrão = o último e-mail ANTES da condição), `;automaticas=true` (conta abertura/clique de máquina; padrão só humano) e `;link=I` (só nas de clique). Texto fora da família é recusado na execução. Condição `email.*` malformada, ou sem e-mail anterior, é 422 ao salvar. Exemplo \"não abriu em 48 h\": e-mail, espera de 48 h, condição `email.nao_abriu`."
          },
          "se_pular_ir": {
            "type": "integer",
            "description": "passo para onde saltar quando a condição é falsa"
          }
        }
      },
      "crm_Gatilho": {
        "type": "object",
        "description": "O que faz um contato entrar na jornada.",
        "required": [
          "tipo"
        ],
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "lead_capturado",
              "campo_mudou",
              "inatividade",
              "evento_pixel",
              "etapa_alterada",
              "conversa_aberta",
              "email_aberto",
              "email_clicado"
            ]
          },
          "args": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "`etapa_alterada` (ADR-0120): `para` obrigatório (etapa de destino), `de` opcional; nenhum outro argumento. `email_aberto`/`email_clicado` (W3): `email_id` obrigatório (id público do e-mail; ausente nunca é curinga), `incluir_automaticas` (`true` conta abertura/clique de máquina; padrão só humano), e `link` (índice, só no clicado). O contato entra a partir de mensagem enviada por uma inscrição de jornada (a correlação vem do evento `events.interno.marketing.email.fato`)."
          }
        }
      },
      "crm_Jornada": {
        "type": "object",
        "description": "Automação completa.",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string",
            "description": "resolvido da sessão; nunca aceito no corpo"
          },
          "nome": {
            "type": "string"
          },
          "gatilho": {
            "$ref": "#/components/schemas/crm_Gatilho"
          },
          "passos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Passo"
            }
          },
          "ativa": {
            "type": "boolean",
            "description": "criada sempre false; muda por PUT .../ativa"
          },
          "layout": {
            "$ref": "#/components/schemas/crm_Layout"
          }
        }
      },
      "crm_JornadaComAvisos": {
        "description": "A jornada salva/ligada, mais `avisos` quando algo não pôde ser conferido (ex.: o serviço de Integrações estava fora ao validar um passo `enviar_conversao` — piso 10: salva mesmo assim).",
        "allOf": [
          {
            "$ref": "#/components/schemas/crm_Jornada"
          },
          {
            "type": "object",
            "properties": {
              "avisos": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        ]
      },
      "crm_CampoCanvas": {
        "type": "object",
        "description": "Um campo de formulário de um nó do construtor.",
        "required": [
          "nome",
          "rotulo",
          "tipo",
          "obrigatorio"
        ],
        "properties": {
          "nome": {
            "type": "string"
          },
          "rotulo": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "texto",
              "decimal",
              "moeda",
              "instalacao"
            ]
          },
          "obrigatorio": {
            "type": "boolean"
          },
          "padrao": {
            "type": "string"
          },
          "dica": {
            "type": "string"
          },
          "apps": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "só em `instalacao`: apps de Integrações aceitos (meta_capi, google_ads, tiktok_events)"
          }
        }
      },
      "crm_AcaoCanvas": {
        "type": "object",
        "required": [
          "tipo",
          "rotulo",
          "grupo",
          "campos"
        ],
        "properties": {
          "tipo": {
            "type": "string"
          },
          "rotulo": {
            "type": "string"
          },
          "grupo": {
            "type": "string",
            "enum": [
              "marketing",
              "bot"
            ]
          },
          "campos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_CampoCanvas"
            }
          }
        }
      },
      "crm_GatilhoCanvas": {
        "type": "object",
        "required": [
          "tipo",
          "rotulo",
          "grupo"
        ],
        "properties": {
          "tipo": {
            "type": "string"
          },
          "rotulo": {
            "type": "string"
          },
          "grupo": {
            "type": "string",
            "enum": [
              "marketing",
              "bot"
            ]
          },
          "args": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_CampoCanvas"
            }
          }
        }
      },
      "crm_CondicaoCanvas": {
        "type": "object",
        "description": "Condição pronta do nó `condicao` (W3 — família `email.*`).",
        "required": [
          "expr",
          "rotulo",
          "grupo"
        ],
        "properties": {
          "expr": {
            "type": "string",
            "description": "texto que vai em `Passo.condicao`, ex.: `email.nao_abriu`"
          },
          "rotulo": {
            "type": "string"
          },
          "grupo": {
            "type": "string",
            "enum": [
              "email"
            ]
          }
        }
      },
      "crm_CatalogoCanvas": {
        "type": "object",
        "description": "Gatilhos, ações e condições prontas do construtor visual, com os campos tipados de cada nó.",
        "required": [
          "gatilhos",
          "acoes"
        ],
        "properties": {
          "gatilhos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_GatilhoCanvas"
            }
          },
          "acoes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_AcaoCanvas"
            }
          },
          "condicoes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_CondicaoCanvas"
            },
            "description": "aditivo (W3): cliente antigo ignora"
          }
        }
      },
      "crm_Posicao": {
        "type": "object",
        "description": "Coordenada de tela de um nó no construtor visual.",
        "properties": {
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          }
        }
      },
      "crm_Layout": {
        "type": "object",
        "description": "Posições dos nós do CONSTRUTOR VISUAL, por chave canônica (\"gatilho\", \"fim\" ou o número do passo). Presentação pura — o motor não a usa; vazio quando a jornada foi criada pelo diálogo.",
        "properties": {
          "posicoes": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/crm_Posicao"
            }
          }
        }
      },
      "crm_NoGrafo": {
        "type": "object",
        "description": "Um nó arrastável do construtor visual.",
        "required": [
          "id",
          "tipo"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "id do cliente, estável dentro do grafo"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "gatilho",
              "agir",
              "esperar",
              "condicao",
              "fim"
            ]
          },
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "gatilho": {
            "$ref": "#/components/schemas/crm_Gatilho"
          },
          "acao": {
            "type": "string",
            "enum": [
              "email",
              "push",
              "webhook",
              "mudar_estagio",
              "entrar_lista",
              "enviar_conversao",
              "responder_conversa",
              "encaminhar_fila"
            ]
          },
          "parametros": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "espera": {
            "type": "integer",
            "format": "int64",
            "description": "duração em nanossegundos (só em `esperar`)"
          },
          "condicao": {
            "type": "string"
          }
        }
      },
      "crm_ArestaGrafo": {
        "type": "object",
        "description": "Liga dois nós pelo id. Ramo vazio segue a espinha; \"senao\" é o salto da condição.",
        "required": [
          "de",
          "para"
        ],
        "properties": {
          "de": {
            "type": "string"
          },
          "para": {
            "type": "string"
          },
          "ramo": {
            "type": "string",
            "enum": [
              "",
              "senao"
            ]
          }
        }
      },
      "crm_Grafo": {
        "type": "object",
        "description": "O fluxo desenhado no construtor visual. O servidor o converte em `gatilho`+`passos`+`layout` (fonte única da conversão) e recusa (422) um desenho que não lineariza.",
        "required": [
          "nos",
          "arestas"
        ],
        "properties": {
          "nome": {
            "type": "string"
          },
          "nos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_NoGrafo"
            }
          },
          "arestas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_ArestaGrafo"
            }
          }
        }
      },
      "crm_PedidoJornada": {
        "type": "object",
        "description": "Corpo de `POST /v1/jornadas` e `PUT /v1/jornadas/{id}`. Sem `id`, `tenant_id` nem `ativa`. Envie `nome`+`gatilho`+`passos` (diálogo) OU `grafo` (construtor visual) — mutuamente exclusivos; mandar os dois é 422.",
        "properties": {
          "nome": {
            "type": "string"
          },
          "gatilho": {
            "$ref": "#/components/schemas/crm_Gatilho"
          },
          "passos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Passo"
            }
          },
          "grafo": {
            "$ref": "#/components/schemas/crm_Grafo"
          }
        }
      },
      "crm_PedidoAtivar": {
        "type": "object",
        "required": [
          "ativa"
        ],
        "properties": {
          "ativa": {
            "type": "boolean"
          }
        }
      },
      "crm_Origem": {
        "type": "object",
        "description": "Atribuição da captura.",
        "properties": {
          "utm_source": {
            "type": "string"
          },
          "utm_medium": {
            "type": "string"
          },
          "utm_campaign": {
            "type": "string"
          },
          "utm_term": {
            "type": "string"
          },
          "utm_content": {
            "type": "string"
          },
          "referrer": {
            "type": "string"
          },
          "dominio": {
            "type": "string"
          },
          "form_id": {
            "type": "string"
          },
          "modo": {
            "type": "string",
            "description": "ex.: manual, formulario"
          },
          "canal_origem": {
            "type": "string",
            "enum": [
              "",
              "organico",
              "pago",
              "email",
              "social",
              "direto",
              "referral",
              "afiliado",
              "offline",
              "outro"
            ],
            "description": "canal da primeira captura (catálogo v2); filtrável"
          },
          "landing_page": {
            "type": "string",
            "description": "landing page da primeira captura (catálogo v2); filtrável (igualdade sem caixa)"
          },
          "extra": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "demais campos de origem do catálogo v2 (utm_id, gbraid, wbraid, fbclid, msclkid, ttclid, fbp, fbc, ga_client_id, pagina_conversao...), first-touch, nunca filtráveis — só no detalhe (`GET /v1/contatos/{id}`): são identificadores de navegador."
          }
        }
      },
      "crm_Qualidade": {
        "type": "object",
        "description": "Sinal sobre o lead; presente só quando avaliado.",
        "properties": {
          "nota": {
            "type": "integer"
          },
          "faixa": {
            "type": "string"
          },
          "datacenter": {
            "type": "boolean"
          }
        }
      },
      "crm_ConsentimentoDoContato": {
        "type": "object",
        "description": "Estado de consentimento resumido, sem o texto integral do aviso.",
        "properties": {
          "base": {
            "type": "string",
            "enum": [
              "consentimento",
              "legitimo_interesse",
              "execucao_contrato"
            ]
          },
          "ativo": {
            "type": "boolean"
          },
          "concedido_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_Contato": {
        "type": "object",
        "description": "Representação pública do contato — a PII vem decifrada; os índices cegos nunca saem.",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "description": "decifrado; ausente se não houver"
          },
          "telefone": {
            "type": "string",
            "description": "decifrado; ausente se não houver"
          },
          "nome": {
            "type": "string"
          },
          "sobrenome": {
            "type": "string"
          },
          "cargo": {
            "type": "string"
          },
          "departamento": {
            "type": "string"
          },
          "pais": {
            "type": "string",
            "description": "ISO 3166-1 alfa-2 (ex.: BR)"
          },
          "estado": {
            "type": "string"
          },
          "cidade": {
            "type": "string"
          },
          "idioma": {
            "type": "string"
          },
          "linkedin": {
            "type": "string"
          },
          "fonte": {
            "type": "string",
            "description": "fonte do lead"
          },
          "genero": {
            "type": "string",
            "enum": [
              "",
              "feminino",
              "masculino",
              "nao-binario",
              "outro",
              "prefiro-nao-informar"
            ],
            "description": "catálogo v2; em claro e filtrável; \"\" = não informado"
          },
          "nivel_hierarquico": {
            "type": "string",
            "enum": [
              "",
              "estagiario",
              "analista",
              "especialista",
              "coordenador",
              "gerente",
              "diretor",
              "vp",
              "c-level",
              "socio",
              "outro"
            ],
            "description": "catálogo v2; em claro e filtrável; \"\" = não informado"
          },
          "empresa": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/crm_EmpresaRef"
              }
            ]
          },
          "dispositivo": {
            "type": "string",
            "enum": [
              "",
              "desktop",
              "mobile",
              "tablet",
              "bot",
              "desconhecido"
            ],
            "description": "do ÚLTIMO envio de formulário; filtrável"
          },
          "so": {
            "type": "string",
            "description": "sistema operacional do último envio (ex.: Android); filtrável"
          },
          "navegador": {
            "type": "string",
            "description": "navegador do último envio (ex.: Chrome); filtrável"
          },
          "ip_pais": {
            "type": "string",
            "description": "ISO-2 do país pelo IP do último envio (DB-IP Lite); filtrável"
          },
          "ip_cidade": {
            "type": "string",
            "description": "cidade pelo IP do último envio"
          },
          "email_status": {
            "type": "string",
            "enum": [
              "",
              "ok",
              "suspeito",
              "dominio_inexistente",
              "descartavel",
              "desconhecido"
            ],
            "description": "verificação do e-mail de identidade feita na captura (sintaxe, MX, descartável, sugestão de correção); \"\" = nunca verificado; filtrável. Trocar o e-mail no PATCH vira ok se o novo é a sugestão, senão desconhecido",
            "example": "suspeito"
          },
          "email_sugestao": {
            "type": "string",
            "description": "correção sugerida para o e-mail (ex. bianchi@gmail.com para bianchi@gmil.com), decifrada — só no detalhe; aplicá-la é um PATCH do e-mail",
            "example": "bianchi@gmail.com"
          },
          "contexto_primeiro": {
            "allOf": [
              {
                "$ref": "#/components/schemas/crm_ContextoDoEnvio"
              }
            ],
            "description": "contexto COMPLETO do PRIMEIRO envio (nunca sobrescrito), decifrado — só no detalhe."
          },
          "contexto_ultimo": {
            "allOf": [
              {
                "$ref": "#/components/schemas/crm_ContextoDoEnvio"
              }
            ],
            "description": "contexto COMPLETO do ÚLTIMO envio, decifrado — só no detalhe."
          },
          "estagio": {
            "type": "string",
            "enum": [
              "novo",
              "contatado",
              "qualificado",
              "cliente",
              "descartado"
            ]
          },
          "dono_id": {
            "type": "string"
          },
          "marcadores": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "atributos": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "campos personalizados, decifrados — só no detalhe (`GET /v1/contatos/{id}`)."
          },
          "dados_pessoais": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "PII forte decifrada — só no detalhe. Qualquer chave de entidade `contato_cifrado` do catálogo v2 (117: whatsapp, cpf, data_nascimento, endereço e prefixos comercial_/cobranca_/entrega_, redes sociais, comercial, marketing, relacionamento...). Campos de papel `consentimento` (aceita_*) são só EVIDÊNCIA do formulário (\"valor @ carimbo\"): nunca viram o consentimento estruturado do contato."
          },
          "origem": {
            "$ref": "#/components/schemas/crm_Origem"
          },
          "qualidade": {
            "$ref": "#/components/schemas/crm_Qualidade"
          },
          "consentimento": {
            "$ref": "#/components/schemas/crm_ConsentimentoDoContato"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_ContextoDoEnvio": {
        "type": "object",
        "description": "Contexto técnico de um envio de formulário (contrato C8). Os campos do servidor vêm do runtime (IP, user agent interpretado, geo por IP — \"IP Geolocation by DB-IP\", CC BY 4.0); os demais, do navegador. Ausente = não coletado. Contém PII (IP, user agent, id de dispositivo, coordenadas): guardado cifrado e zerado na anonimização.",
        "properties": {
          "ip": {
            "type": "string"
          },
          "ip_versao": {
            "type": "integer",
            "enum": [
              4,
              6
            ]
          },
          "user_agent": {
            "type": "string"
          },
          "navegador": {
            "type": "string"
          },
          "navegador_versao": {
            "type": "string"
          },
          "so": {
            "type": "string"
          },
          "so_versao": {
            "type": "string"
          },
          "dispositivo": {
            "type": "string",
            "enum": [
              "desktop",
              "mobile",
              "tablet",
              "bot",
              "desconhecido"
            ]
          },
          "idioma_http": {
            "type": "string",
            "description": "Accept-Language"
          },
          "geo": {
            "type": "object",
            "properties": {
              "pais": {
                "type": "string",
                "description": "ISO-2"
              },
              "regiao": {
                "type": "string"
              },
              "cidade": {
                "type": "string"
              },
              "lat": {
                "type": "number"
              },
              "lon": {
                "type": "number"
              },
              "fuso": {
                "type": "string",
                "description": "IANA"
              }
            }
          },
          "rede": {
            "type": "object",
            "properties": {
              "asn": {
                "type": "string"
              },
              "org": {
                "type": "string"
              }
            }
          },
          "recebida_em": {
            "type": "string",
            "format": "date-time"
          },
          "pagina_url": {
            "type": "string"
          },
          "pagina_titulo": {
            "type": "string"
          },
          "referrer": {
            "type": "string"
          },
          "idiomas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "fuso": {
            "type": "string",
            "description": "IANA, do navegador"
          },
          "tela": {
            "type": "string",
            "example": "1920x1080"
          },
          "janela": {
            "type": "string",
            "example": "1280x720"
          },
          "dpr": {
            "type": "number"
          },
          "cores": {
            "type": "integer"
          },
          "memoria_gb": {
            "type": "number"
          },
          "conexao": {
            "type": "string",
            "example": "4g"
          },
          "toque": {
            "type": "boolean"
          },
          "plataforma": {
            "type": "string"
          },
          "mobile": {
            "type": "boolean"
          },
          "marcas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "cookies": {
            "type": "boolean"
          },
          "dnt": {
            "type": "string"
          },
          "tempo_no_form_s": {
            "type": "number"
          },
          "visitas": {
            "type": "integer"
          },
          "dispositivo_id": {
            "type": "string",
            "description": "UUID persistido no navegador (localStorage do site)"
          },
          "primeira_visita": {
            "type": "object",
            "properties": {
              "em": {
                "type": "string",
                "format": "date-time"
              },
              "url": {
                "type": "string"
              },
              "referrer": {
                "type": "string"
              },
              "utm": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "click_ids": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "utm": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "source, medium, campaign, term, content"
          },
          "click_ids": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "gclid, gbraid, wbraid, fbclid, msclkid, ttclid, li_fat_id, twclid, yclid, dclid, epik, sccid, igshid"
          },
          "verificacao_email": {
            "type": "array",
            "description": "verificação de cada campo de e-mail do envio, feita pelo runtime",
            "items": {
              "type": "object",
              "properties": {
                "campo": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "ok",
                    "suspeito",
                    "dominio_inexistente",
                    "descartavel",
                    "desconhecido"
                  ]
                },
                "sugestao": {
                  "type": "string",
                  "description": "correção sugerida (e-mail)"
                },
                "mx": {
                  "type": "boolean"
                },
                "descartavel": {
                  "type": "boolean"
                },
                "papel": {
                  "type": "boolean",
                  "description": "conta de função (contato@, vendas@…)"
                },
                "gratuito": {
                  "type": "boolean",
                  "description": "provedor gratuito (gmail, outlook…)"
                }
              }
            }
          }
        },
        "example": {
          "ip": "203.0.113.9",
          "navegador": "Chrome",
          "so": "Android",
          "dispositivo": "mobile",
          "geo": {
            "pais": "BR",
            "regiao": "Pernambuco",
            "cidade": "Recife",
            "fuso": "America/Recife"
          },
          "pagina_url": "https://loja.test/contato",
          "idiomas": [
            "pt-BR"
          ],
          "tempo_no_form_s": 42.5
        }
      },
      "crm_PedidoContato": {
        "type": "object",
        "description": "Corpo de `POST`/`PATCH` de contato. Sem `id`, `tenant_id` nem índices (mass assignment vira 400).",
        "properties": {
          "email": {
            "type": "string"
          },
          "telefone": {
            "type": "string"
          },
          "nome": {
            "type": "string"
          },
          "estagio": {
            "type": "string",
            "enum": [
              "novo",
              "contatado",
              "qualificado",
              "cliente",
              "descartado"
            ]
          },
          "dono_id": {
            "type": "string"
          },
          "marcadores": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "lista vazia remove todos"
          },
          "sobrenome": {
            "type": "string",
            "description": "ausente preserva; \"\" limpa (vale para todos os campos abaixo)"
          },
          "cargo": {
            "type": "string"
          },
          "departamento": {
            "type": "string"
          },
          "pais": {
            "type": "string",
            "description": "ISO 3166-1 alfa-2; outro formato → 422"
          },
          "estado": {
            "type": "string"
          },
          "cidade": {
            "type": "string"
          },
          "idioma": {
            "type": "string"
          },
          "linkedin": {
            "type": "string"
          },
          "genero": {
            "type": "string",
            "enum": [
              "",
              "feminino",
              "masculino",
              "nao-binario",
              "outro",
              "prefiro-nao-informar"
            ],
            "description": "catálogo v2; maiúsculas e acento são normalizados; fora das opções → 422"
          },
          "nivel_hierarquico": {
            "type": "string",
            "enum": [
              "",
              "estagiario",
              "analista",
              "especialista",
              "coordenador",
              "gerente",
              "diretor",
              "vp",
              "c-level",
              "socio",
              "outro"
            ],
            "description": "catálogo v2; fora das opções → 422"
          },
          "fonte": {
            "type": "string"
          },
          "empresa_id": {
            "type": "string",
            "description": "id de uma empresa DESTA conta (escolhida no seletor); \"\" solta; alheia/inexistente → 422"
          },
          "dados_pessoais": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "só as chaves do catálogo (entidade contato_cifrado); valor \"\" remove; chave desconhecida → 422."
          },
          "atributos": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "campos personalizados; valor \"\" remove; até 60 chaves."
          }
        },
        "example": {
          "email": "ana@test.asender.com.br",
          "telefone": "+55 11 91234-5678",
          "nome": "Ana",
          "sobrenome": "Souza",
          "cargo": "Diretora Comercial",
          "pais": "BR",
          "cidade": "São Paulo",
          "empresa_id": "emp_3f9a0c1d2e4b5a6c",
          "dados_pessoais": {
            "cpf": "12345678909"
          },
          "atributos": {
            "Interesse": "Plano Pro"
          }
        }
      },
      "crm_EmpresaRef": {
        "type": "object",
        "description": "a empresa do contato pelo NOME (a tela nunca mostra o id).",
        "properties": {
          "id": {
            "type": "string"
          },
          "nome": {
            "type": "string"
          }
        }
      },
      "crm_Empresa": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "emp_3f9a0c1d2e4b5a6c"
          },
          "nome": {
            "type": "string",
            "example": "ACME Ltda"
          },
          "cnpj": {
            "type": "string",
            "description": "14 dígitos",
            "example": "11222333000181"
          },
          "site": {
            "type": "string",
            "example": "https://acme.com.br"
          },
          "dominio": {
            "type": "string",
            "example": "acme.com.br"
          },
          "setor": {
            "type": "string",
            "example": "Varejo"
          },
          "porte": {
            "type": "string",
            "enum": [
              "",
              "mei",
              "micro",
              "pequena",
              "media",
              "grande"
            ]
          },
          "funcionarios": {
            "type": "string",
            "enum": [
              "",
              "1-10",
              "11-50",
              "51-200",
              "201-500",
              "501-1000",
              "1001+"
            ]
          },
          "faturamento": {
            "type": "string",
            "enum": [
              "",
              "ate-360k",
              "360k-4.8m",
              "4.8m-30m",
              "30m-300m",
              "300m+"
            ]
          },
          "pais": {
            "type": "string",
            "example": "BR"
          },
          "estado": {
            "type": "string"
          },
          "cidade": {
            "type": "string"
          },
          "tipo_empresa": {
            "type": "string",
            "enum": [
              "",
              "prospect",
              "cliente",
              "parceiro",
              "revendedor",
              "fornecedor",
              "concorrente",
              "investidor",
              "outro"
            ],
            "description": "catálogo v2; filtrável"
          },
          "cnae": {
            "type": "string",
            "description": "catálogo v2; filtrável por prefixo"
          },
          "segmento": {
            "type": "string",
            "description": "catálogo v2; filtrável (igualdade sem caixa)"
          },
          "regime_tributario": {
            "type": "string",
            "enum": [
              "",
              "mei",
              "simples-nacional",
              "lucro-presumido",
              "lucro-real",
              "outro"
            ],
            "description": "catálogo v2; filtrável"
          },
          "ano_fundacao": {
            "type": "integer",
            "description": "0 = não informado; filtrável por faixa (ano_de/ano_ate)"
          },
          "qtd_funcionarios": {
            "type": "integer",
            "description": "0 = não informado; filtrável por faixa (qtd_min/qtd_max)"
          },
          "dados": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "campos descritivos do catálogo v2 (razao_social, nome_fantasia, inscricao_estadual, inscricao_municipal, natureza_juridica, telefone_empresa, email_empresa, redes *_empresa, descricao_empresa, logo_empresa); `{}` quando não há. Não filtráveis."
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_PedidoEmpresa": {
        "type": "object",
        "description": "Corpo de POST/PATCH de empresa. Ausente preserva, \"\" limpa. CNPJ aceita máscara e é validado (DV); nome vazio com CNPJ vira o CNPJ formatado. Campo desconhecido (ex.: tenant_id) → 400.",
        "properties": {
          "nome": {
            "type": "string"
          },
          "cnpj": {
            "type": "string"
          },
          "site": {
            "type": "string"
          },
          "setor": {
            "type": "string"
          },
          "porte": {
            "type": "string"
          },
          "funcionarios": {
            "type": "string"
          },
          "faturamento": {
            "type": "string"
          },
          "pais": {
            "type": "string"
          },
          "estado": {
            "type": "string"
          },
          "cidade": {
            "type": "string"
          },
          "tipo_empresa": {
            "type": "string"
          },
          "cnae": {
            "type": "string"
          },
          "segmento": {
            "type": "string"
          },
          "regime_tributario": {
            "type": "string"
          },
          "ano_fundacao": {
            "type": "integer",
            "description": "0 limpa; 1000..2200"
          },
          "qtd_funcionarios": {
            "type": "integer",
            "description": "0 limpa"
          },
          "dados": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "MESCLA por chave (valor \"\" remove); só chaves descritivas de empresa do catálogo — outra chave, ou valor do tipo errado (URL, e-mail, telefone), → 422."
          }
        },
        "example": {
          "nome": "ACME Ltda",
          "cnpj": "11.222.333/0001-81",
          "porte": "media",
          "site": "acme.com.br",
          "tipo_empresa": "cliente",
          "dados": {
            "razao_social": "ACME Comércio Ltda"
          }
        }
      },
      "crm_ListaEmpresas": {
        "type": "object",
        "required": [
          "empresas",
          "proximo"
        ],
        "properties": {
          "empresas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Empresa"
            }
          },
          "proximo": {
            "type": "string"
          }
        }
      },
      "crm_CatalogoDeCampos": {
        "type": "object",
        "description": "o contrato asender_docs/contratos/campos-contato.json.",
        "properties": {
          "versao": {
            "type": "integer",
            "example": 2
          },
          "descricao": {
            "type": "string"
          },
          "papeis": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "significado de cada `papel` (dado, consentimento, derivado)."
          },
          "grupos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "chave": {
                  "type": "string"
                },
                "rotulo": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "campos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "chave": {
                  "type": "string",
                  "example": "cidade"
                },
                "grupo": {
                  "type": "string",
                  "example": "endereco"
                },
                "tipo": {
                  "type": "string",
                  "example": "texto"
                },
                "entidade": {
                  "type": "string",
                  "enum": [
                    "contato",
                    "contato_cifrado",
                    "identidade",
                    "empresa",
                    "origem"
                  ]
                },
                "filtravel": {
                  "type": "boolean"
                },
                "pii": {
                  "type": "boolean"
                },
                "papel": {
                  "type": "string",
                  "enum": [
                    "dado",
                    "consentimento",
                    "derivado"
                  ],
                  "description": "dado = a ingestão grava na entidade do campo; consentimento = aceite declarado no formulário, só EVIDÊNCIA (nunca levanta supressão nem libera envio); derivado = o sistema preenche — fora do construtor e do seletor de importação, e o valor vindo de formulário é ignorado."
                },
                "unico": {
                  "type": "boolean",
                  "description": "true = só UM campo do formulário pode ocupar a chave; false (mensagem, observacoes) = vários, concatenados na ingestão."
                },
                "mascara": {
                  "type": "string",
                  "nullable": true,
                  "description": "tipo de campo que o construtor cria ao aceitar a sugestão; igual a `tipo`, exceto `texto` → null."
                },
                "promove_a": {
                  "type": "string",
                  "enum": [
                    "email",
                    "telefone"
                  ],
                  "description": "presente só em whatsapp, celular, telefone_comercial, telefone_residencial (→ telefone) e email_comercial, email_pessoal (→ email): com o slot de identidade livre no formulário, o PRIMEIRO campo desse tipo o ocupa."
                },
                "sinonimos": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "títulos que um humano usaria, normalizados (minúsculas, sem acento, só [a-z0-9] e espaço)."
                },
                "opcoes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "rotulo": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  },
                  "description": "pt e en; os demais idiomas caem em en, depois pt."
                }
              }
            }
          }
        }
      },
      "crm_ListaContatos": {
        "type": "object",
        "required": [
          "contatos",
          "proximo"
        ],
        "properties": {
          "contatos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Contato"
            }
          },
          "proximo": {
            "type": "string",
            "description": "cursor opaco da próxima página; vazio = acabou"
          }
        }
      },
      "crm_PedidoApagamento": {
        "type": "object",
        "properties": {
          "motivo": {
            "type": "string",
            "description": "opcional; default \"apagado pelo usuário\""
          }
        }
      },
      "crm_PedidoExclusaoNovo": {
        "type": "object",
        "required": [
          "contato_id",
          "solicitante"
        ],
        "properties": {
          "contato_id": {
            "type": "string",
            "description": "id público do contato (con_…)"
          },
          "solicitante": {
            "type": "string",
            "maxLength": 200,
            "description": "como o titular se identificou (PII; redigido após a execução)"
          },
          "canal": {
            "type": "string",
            "enum": [
              "formulario",
              "email",
              "telefone",
              "chat",
              "presencial",
              "outro"
            ],
            "default": "outro"
          },
          "motivo": {
            "type": "string",
            "maxLength": 1000
          }
        }
      },
      "crm_PedidoExclusaoDecisao": {
        "type": "object",
        "properties": {
          "motivo": {
            "type": "string",
            "maxLength": 1000,
            "description": "obrigatório ao recusar; opcional ao aprovar"
          }
        }
      },
      "crm_PedidoExclusao": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ped_…"
          },
          "contato_id": {
            "type": "string"
          },
          "solicitante": {
            "type": "string"
          },
          "canal": {
            "type": "string"
          },
          "motivo": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pendente",
              "aprovado",
              "recusado",
              "executado"
            ]
          },
          "criado_por": {
            "type": "string"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "decidido_por": {
            "type": "string"
          },
          "decisao_motivo": {
            "type": "string"
          },
          "decidido_em": {
            "type": "string",
            "format": "date-time"
          },
          "executado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_PedidosExclusaoPagina": {
        "type": "object",
        "properties": {
          "itens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_PedidoExclusao"
            }
          },
          "proximo": {
            "type": "string",
            "description": "cursor opaco; vazio = acabou"
          }
        }
      },
      "crm_Atividade": {
        "type": "object",
        "description": "Uma entrada da timeline.",
        "properties": {
          "id": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "description": "ex.: nota_adicionada, dados_exportados, contato_anonimizado"
          },
          "resumo": {
            "type": "string"
          },
          "autor_id": {
            "type": "string",
            "description": "ausente quando foi o sistema"
          },
          "por_sistema": {
            "type": "boolean"
          },
          "dados": {
            "type": "object",
            "additionalProperties": true,
            "description": "Chaves por tipo. `conversao_enviada` (ADR-0120): `app` (slug do app, ausente se nem Integrações nem o cache do passo o souberam), `instalacao_nome`, `evento`, `valor`, `moeda`. `conversao_resultado`: `app`, `instalacao_nome`, `desfecho`, `id_externo`, `motivo`."
          },
          "ocorreu_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_ListaAtividades": {
        "type": "object",
        "required": [
          "atividades",
          "proximo",
          "total"
        ],
        "properties": {
          "atividades": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Atividade"
            }
          },
          "proximo": {
            "type": "string",
            "description": "cursor opaco; vazio = acabou"
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "crm_PedidoNota": {
        "type": "object",
        "required": [
          "texto"
        ],
        "properties": {
          "texto": {
            "type": "string",
            "description": "até 4000 caracteres"
          }
        }
      },
      "crm_ConversaDoContato": {
        "type": "object",
        "description": "Item RASO da projeção local — sem estado nem prévia de mensagem (ver a descrição da rota). O detalhe completo mora no asender_atendimento_api.",
        "properties": {
          "conversa_id": {
            "type": "string",
            "description": "id PÚBLICO e opaco, do asender_atendimento_api"
          },
          "canal": {
            "type": "string",
            "enum": [
              "form",
              "email",
              "whatsapp",
              "chat"
            ]
          },
          "vinculado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_ListaConversasDoContato": {
        "type": "object",
        "required": [
          "itens"
        ],
        "properties": {
          "itens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_ConversaDoContato"
            }
          }
        }
      },
      "crm_IdentidadeDoContato": {
        "type": "object",
        "required": [
          "id",
          "tipo",
          "valor_mascarado",
          "verificada",
          "criada_em"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "id público opaco (idn_…)"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "email",
              "telefone",
              "whatsapp",
              "chat_visitante",
              "telegram",
              "whatsapp_lid"
            ]
          },
          "valor_mascarado": {
            "type": "string",
            "description": "ex.: a***@dominio.com, +55*********78, tg:******789, lid:***123 — o valor em claro nunca é devolvido"
          },
          "verificada": {
            "type": "boolean"
          },
          "fonte": {
            "type": "string",
            "description": "formulario, importacao, manual, conversa:…; ausente nas espelhadas das colunas escalares"
          },
          "criada_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_ListaIdentidadesDoContato": {
        "type": "object",
        "required": [
          "itens"
        ],
        "properties": {
          "itens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_IdentidadeDoContato"
            }
          }
        }
      },
      "crm_PedidoIdentidadeManual": {
        "type": "object",
        "required": [
          "tipo",
          "valor"
        ],
        "additionalProperties": false,
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "telefone",
              "email",
              "telegram",
              "whatsapp_lid"
            ]
          },
          "valor": {
            "type": "string",
            "description": "telefone E.164 (+5511912345678), e-mail, tg:<dígitos> ou lid:<id>"
          }
        }
      },
      "crm_PedidoDesvinculo": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "motivo": {
            "type": "string",
            "maxLength": 500,
            "description": "texto livre; fica só na linha da identidade, não na timeline"
          }
        }
      },
      "crm_ConflitoDeIdentidade": {
        "type": "object",
        "required": [
          "erro",
          "contato_id",
          "sugestoes_url"
        ],
        "properties": {
          "erro": {
            "type": "string"
          },
          "contato_id": {
            "type": "string",
            "description": "id público do contato que já tem o endereço (a UI oferece a mesclagem)"
          },
          "sugestoes_url": {
            "type": "string",
            "description": "a fila onde a colisão foi enfileirada (/v1/identidades/sugestoes)"
          }
        }
      },
      "crm_IdentidadeResolvida": {
        "type": "object",
        "required": [
          "achou"
        ],
        "properties": {
          "achou": {
            "type": "boolean"
          },
          "contato_id": {
            "type": "string",
            "description": "ausente quando achou é false"
          }
        }
      },
      "crm_Consentimento": {
        "type": "object",
        "description": "Preferência da pessoa, canal a canal, no positivo (`recebe`).",
        "required": [
          "recebe"
        ],
        "properties": {
          "recebe": {
            "type": "object",
            "additionalProperties": {
              "type": "boolean"
            },
            "description": "uma entrada por canal, sempre as quatro; ex.: { email: true, sms: false, push: true, whatsapp: true }"
          },
          "fuso": {
            "type": "string",
            "description": "fuso da pessoa, quando conhecido"
          }
        }
      },
      "crm_PedidoConsentimento": {
        "type": "object",
        "description": "Um canal por vez; `recebe` ausente é 422.",
        "required": [
          "canal",
          "recebe"
        ],
        "properties": {
          "canal": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "push",
              "whatsapp"
            ],
            "description": "webhook NÃO entra: alcança um sistema do cliente, não uma pessoa"
          },
          "recebe": {
            "type": "boolean",
            "description": "true volta a receber, false sai"
          },
          "origem": {
            "type": "string",
            "description": "quem pediu; default \"painel\""
          }
        }
      },
      "crm_Criterio": {
        "type": "object",
        "description": "Uma condição sobre um campo da allowlist.",
        "required": [
          "campo",
          "operador"
        ],
        "properties": {
          "campo": {
            "type": "string",
            "enum": [
              "estagio",
              "dono",
              "nome",
              "origem_source",
              "origem_medium",
              "origem_campanha",
              "origem_dominio",
              "origem_form",
              "origem_modo",
              "criado_em",
              "atualizado_em",
              "marcador",
              "consentimento"
            ]
          },
          "operador": {
            "type": "string",
            "enum": [
              "igual",
              "diferente",
              "contem",
              "comeca_com",
              "existe",
              "nao_existe",
              "maior_que",
              "menor_que",
              "tem_marcador",
              "nao_tem_marcador"
            ]
          },
          "valor": {
            "type": "string",
            "description": "não exigido por existe/nao_existe"
          }
        }
      },
      "crm_Segmento": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string",
            "description": "resolvido da sessão"
          },
          "nome": {
            "type": "string"
          },
          "conjuncao": {
            "type": "string",
            "enum": [
              "e",
              "ou"
            ],
            "description": "padrão `e`"
          },
          "criterios": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Criterio"
            }
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_PedidoSegmento": {
        "type": "object",
        "description": "Corpo de criar/atualizar/prévia. Sem `id`, `tenant_id` nem `criado_em`.",
        "required": [
          "nome",
          "criterios"
        ],
        "properties": {
          "nome": {
            "type": "string"
          },
          "conjuncao": {
            "type": "string",
            "enum": [
              "e",
              "ou"
            ]
          },
          "criterios": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Criterio"
            }
          }
        }
      },
      "crm_SegmentoComAlcance": {
        "type": "object",
        "required": [
          "segmento",
          "alcance"
        ],
        "properties": {
          "segmento": {
            "$ref": "#/components/schemas/crm_Segmento"
          },
          "alcance": {
            "type": "integer",
            "description": "quantos contatos alcança agora; -1 = não consegui contar"
          }
        }
      },
      "crm_ListaSegmentos": {
        "type": "object",
        "required": [
          "segmentos"
        ],
        "properties": {
          "segmentos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Segmento"
            }
          }
        }
      },
      "crm_Alcance": {
        "type": "object",
        "required": [
          "alcance"
        ],
        "properties": {
          "alcance": {
            "type": "integer"
          }
        }
      },
      "crm_Alerta": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "nome": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "sem_leads",
              "queda_qualidade"
            ]
          },
          "limiar": {
            "type": "number"
          },
          "janela_minutos": {
            "type": "integer"
          },
          "estado": {
            "type": "string",
            "enum": [
              "calmo",
              "disparado"
            ]
          },
          "ativo": {
            "type": "boolean"
          },
          "destino_id": {
            "type": "string"
          },
          "disparado_em": {
            "type": "string",
            "format": "date-time"
          },
          "avaliado_em": {
            "type": "string",
            "format": "date-time"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_AlertaEvento": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "alerta_id": {
            "type": "string"
          },
          "tipo": {
            "type": "string"
          },
          "motivo": {
            "type": "string"
          },
          "valor": {
            "type": "number"
          },
          "ocorreu_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_PedidoAlerta": {
        "type": "object",
        "required": [
          "nome",
          "tipo"
        ],
        "properties": {
          "nome": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "sem_leads",
              "queda_qualidade"
            ]
          },
          "limiar": {
            "type": "number"
          },
          "janela_minutos": {
            "type": "integer"
          },
          "destino_id": {
            "type": "string"
          },
          "ativo": {
            "type": "boolean"
          }
        }
      },
      "crm_ListaAlertas": {
        "type": "object",
        "required": [
          "alertas"
        ],
        "properties": {
          "alertas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Alerta"
            }
          }
        }
      },
      "crm_ListaEventosAlerta": {
        "type": "object",
        "required": [
          "eventos"
        ],
        "properties": {
          "eventos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_AlertaEvento"
            }
          }
        }
      },
      "crm_Destino": {
        "type": "object",
        "description": "Destino de lead. O segredo nunca volta — só `tem_segredo`.",
        "properties": {
          "id": {
            "type": "string"
          },
          "nome": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "webhook",
              "conversao"
            ]
          },
          "provedor": {
            "type": "string",
            "description": "só em conversao (ex.: meta)"
          },
          "config": {
            "type": "object",
            "additionalProperties": true,
            "description": "ex.: { pixel_id }; o token nunca aparece"
          },
          "url": {
            "type": "string"
          },
          "ativo": {
            "type": "boolean"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "tem_segredo": {
            "type": "boolean"
          }
        }
      },
      "crm_Entrega": {
        "type": "object",
        "description": "Tentativa de levar um evento a um destino. O payload não volta (carrega PII).",
        "properties": {
          "id": {
            "type": "string"
          },
          "evento": {
            "type": "string"
          },
          "estado": {
            "type": "string"
          },
          "tentativas": {
            "type": "integer"
          },
          "ultimo_codigo": {
            "type": "integer"
          },
          "ultimo_erro": {
            "type": "string"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "entregue_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_PedidoDestino": {
        "type": "object",
        "description": "Corpo de criar/atualizar. O `tipo` vem da ROTA, não do corpo.",
        "properties": {
          "nome": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "http/https; rede interna é recusada (422). Ignorado em conversao"
          },
          "segredo": {
            "type": "string",
            "description": "cifrado no servidor; ausente no PUT preserva o gravado"
          },
          "ativo": {
            "type": "boolean"
          },
          "provedor": {
            "type": "string",
            "description": "só conversao (hoje: meta)"
          },
          "config": {
            "type": "object",
            "additionalProperties": true,
            "description": "só conversao; exige pixel_id"
          }
        }
      },
      "crm_ListaDestinos": {
        "type": "object",
        "required": [
          "destinos"
        ],
        "properties": {
          "destinos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Destino"
            }
          }
        }
      },
      "crm_ListaIntegracoes": {
        "type": "object",
        "required": [
          "integracoes"
        ],
        "properties": {
          "integracoes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Destino"
            }
          }
        }
      },
      "crm_ListaEntregas": {
        "type": "object",
        "required": [
          "entregas"
        ],
        "properties": {
          "entregas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/crm_Entrega"
            }
          }
        }
      },
      "crm_TesteEnfileirado": {
        "type": "object",
        "required": [
          "enfileiradas"
        ],
        "properties": {
          "enfileiradas": {
            "type": "integer"
          },
          "aviso": {
            "type": "string"
          }
        }
      },
      "crm_Retencao": {
        "type": "object",
        "description": "Política de retenção. `dias: null` = guardar indefinidamente.",
        "properties": {
          "dias": {
            "type": [
              "integer",
              "null"
            ],
            "description": "30 a 3650, ou null"
          },
          "aplicada_em": {
            "type": "string",
            "format": "date-time"
          },
          "anonimizados_total": {
            "type": "integer",
            "format": "int64"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_RespostaRetencao": {
        "type": "object",
        "required": [
          "retencao"
        ],
        "properties": {
          "retencao": {
            "$ref": "#/components/schemas/crm_Retencao"
          }
        }
      },
      "crm_PedidoRetencao": {
        "type": "object",
        "description": "`dias` ausente é 400; `null` desliga a expiração.",
        "required": [
          "dias"
        ],
        "properties": {
          "dias": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "crm_Uso": {
        "type": "object",
        "description": "Consumo da conta. Os números locais sempre voltam; a quota depende do core.",
        "properties": {
          "periodo": {
            "type": "string",
            "description": "AAAA-MM"
          },
          "contatos": {
            "type": "integer",
            "format": "int64"
          },
          "contatos_no_periodo": {
            "type": "integer",
            "format": "int64"
          },
          "anonimizados": {
            "type": "integer",
            "format": "int64"
          },
          "quota_mensal": {
            "type": [
              "integer",
              "null"
            ]
          },
          "quota_conhecida": {
            "type": "boolean",
            "description": "false separa \"sem limite\" de \"core fora\""
          },
          "core_disponivel": {
            "type": "boolean"
          },
          "emails_enviados": {
            "type": "integer",
            "format": "int64"
          },
          "sms_enviados": {
            "type": "integer",
            "format": "int64"
          },
          "push_enviados": {
            "type": "integer",
            "format": "int64"
          },
          "chamadas_api": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "crm_RespostaUso": {
        "type": "object",
        "required": [
          "uso"
        ],
        "properties": {
          "uso": {
            "$ref": "#/components/schemas/crm_Uso"
          }
        }
      },
      "crm_Projeto": {
        "type": "object",
        "description": "Um projeto do kanban. Só a logo está detalhada aqui; as demais rotas de /v1/projetos usam o mesmo objeto.",
        "required": [
          "id",
          "coluna_id",
          "titulo",
          "descricao",
          "ordem",
          "dono_id",
          "prazo",
          "valor",
          "tem_logo",
          "logo_versao",
          "criado_em",
          "atualizado_em"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "coluna_id": {
            "type": "string"
          },
          "cliente_id": {
            "type": "string"
          },
          "cliente_nome": {
            "type": "string"
          },
          "titulo": {
            "type": "string"
          },
          "descricao": {
            "type": "string"
          },
          "ordem": {
            "type": "integer"
          },
          "dono_id": {
            "type": "string"
          },
          "prazo": {
            "type": "string",
            "description": "AAAA-MM-DD ou vazio"
          },
          "valor": {
            "type": "string",
            "description": "decimal canônico ou vazio"
          },
          "tem_logo": {
            "type": "boolean",
            "description": "o projeto tem logo customizada (campo aditivo)"
          },
          "logo_versao": {
            "type": "string",
            "description": "primeiros 12 hex do sha256 da logo; vazio sem logo — use como cache-buster do GET /logo (campo aditivo)"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "crm_ErroLogo": {
        "type": "object",
        "description": "Erro do envio de logo — o envelope `erro` mais um `codigo` estável.",
        "required": [
          "erro",
          "codigo"
        ],
        "properties": {
          "erro": {
            "type": "string"
          },
          "codigo": {
            "type": "string",
            "enum": [
              "ArquivoGrande",
              "FormatoInvalido",
              "ArquivoInfectado",
              "AntivirusIndisponivel"
            ]
          }
        }
      },
      "crm_PedidoLogo": {
        "type": "object",
        "required": [
          "arquivo"
        ],
        "properties": {
          "arquivo": {
            "type": "string",
            "format": "binary",
            "description": "PNG ou JPEG, até 512 KiB, 32..2048 px por lado. O tipo é detectado pelos bytes (Content-Type e extensão do cliente são ignorados); a imagem é re-codificada (EXIF e metadados somem). SVG e outros tipos são recusados."
          }
        }
      },
      "pages_EstadoCloudflare": {
        "type": "object",
        "description": "O que a tela pode oferecer para o domínio na Cloudflare. O token NUNCA volta (nem cifrado). `domain_connect.motivo` explica por que o botão de 1 clique não aparece — consulta_falhou | fora_da_cloudflare | sem_domain_connect | sem_chave | template_pendente.",
        "properties": {
          "zona": {
            "type": "string",
            "example": "loja.com.br"
          },
          "na_cloudflare": {
            "type": "boolean"
          },
          "consulta_falhou": {
            "type": "boolean"
          },
          "domain_connect": {
            "type": "object",
            "properties": {
              "disponivel": {
                "type": "boolean"
              },
              "motivo": {
                "type": "string",
                "enum": [
                  "consulta_falhou",
                  "fora_da_cloudflare",
                  "sem_domain_connect",
                  "sem_chave",
                  "template_pendente"
                ]
              }
            }
          },
          "token": {
            "type": "object",
            "properties": {
              "disponivel": {
                "type": "boolean",
                "description": "o ambiente tem a chave de cifra (ENCRYPTION_KEY)"
              },
              "configurado": {
                "type": "boolean"
              },
              "zona": {
                "type": "string"
              },
              "atualizado_em": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "ultima_aplicacao": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/pages_AplicacaoDNS"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "pages_AplicacaoDNS": {
        "type": "object",
        "description": "Uma linha da trilha de auditoria (append-only) das ações no DNS.",
        "properties": {
          "ator": {
            "type": "string"
          },
          "metodo": {
            "type": "string",
            "enum": [
              "token",
              "domain_connect"
            ]
          },
          "evento": {
            "type": "string",
            "enum": [
              "token_salvo",
              "token_removido",
              "aplicacao",
              "url_gerada"
            ]
          },
          "zona": {
            "type": "string"
          },
          "resultado": {
            "type": "string",
            "enum": [
              "ok",
              "parcial",
              "erro"
            ]
          },
          "registros": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_AcaoDNS"
            }
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "pages_AcaoDNS": {
        "type": "object",
        "description": "O que a aplicação fez num registro. Nunca há \"apagado\": registro alheio que atrapalha vira `conflito` com a instrução em `detalhe`.",
        "properties": {
          "resultado": {
            "type": "string",
            "enum": [
              "mantido",
              "criado",
              "atualizado",
              "conflito",
              "falhou"
            ]
          },
          "tipo": {
            "type": "string",
            "example": "CNAME"
          },
          "nome": {
            "type": "string"
          },
          "valor": {
            "type": "string"
          },
          "papel": {
            "type": "string"
          },
          "anterior": {
            "type": "string",
            "description": "valor antes de um `atualizado` (para desfazer à mão)"
          },
          "detalhe": {
            "type": "string"
          }
        }
      },
      "pages_RelatorioCloudflare": {
        "type": "object",
        "properties": {
          "zona": {
            "type": "string"
          },
          "ok": {
            "type": "boolean",
            "description": "nenhum conflito nem falha"
          },
          "registros": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_AcaoDNS"
            }
          }
        }
      },
      "pages_UsoDeCota": {
        "type": "object",
        "required": [
          "limite",
          "usado"
        ],
        "properties": {
          "limite": {
            "type": [
              "integer",
              "null"
            ],
            "description": "null = ilimitado"
          },
          "usado": {
            "type": "integer"
          }
        }
      },
      "pages_CotaExcedida": {
        "type": "object",
        "required": [
          "recurso",
          "limite",
          "usado"
        ],
        "properties": {
          "erro": {
            "type": "string"
          },
          "recurso": {
            "type": "string",
            "enum": [
              "pages_subdominios",
              "pages_dominios_principais"
            ]
          },
          "limite": {
            "type": "integer"
          },
          "usado": {
            "type": "integer"
          }
        }
      },
      "pages_DominioGratis": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "host": {
            "type": "string",
            "example": "minhaloja.apages.xyz"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "gratis"
            ]
          },
          "classe": {
            "type": "string",
            "enum": [
              "subdominio"
            ]
          },
          "verificado": {
            "type": "boolean"
          },
          "pronto": {
            "type": "boolean"
          },
          "verificado_em": {
            "type": "string",
            "format": "date-time"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "posse": {
            "type": "object",
            "properties": {
              "ok": {
                "type": "boolean"
              }
            }
          },
          "apontamento": {
            "type": "object",
            "properties": {
              "ok": {
                "type": "boolean"
              }
            }
          },
          "tls": {
            "type": "object",
            "properties": {
              "estado": {
                "type": "string"
              }
            }
          }
        }
      },
      "pages_ResumoDeFerramenta": {
        "type": "object",
        "description": "Contrato comum de resumo de ferramenta (plano 2026-10-02) — o mesmo formato em toda ferramenta.",
        "required": [
          "ferramenta",
          "periodo",
          "indicadores",
          "series"
        ],
        "properties": {
          "ferramenta": {
            "type": "string",
            "description": "slug da ferramenta (crm, mail, pages, atendimento, …)"
          },
          "periodo": {
            "type": "object",
            "required": [
              "de",
              "ate",
              "chave"
            ],
            "properties": {
              "de": {
                "type": "string",
                "format": "date",
                "description": "primeiro dia da janela (inclusivo)"
              },
              "ate": {
                "type": "string",
                "format": "date",
                "description": "último dia da janela (inclusivo)"
              },
              "chave": {
                "type": "string",
                "enum": [
                  "7d",
                  "30d",
                  "90d"
                ]
              }
            }
          },
          "indicadores": {
            "type": "array",
            "minItems": 1,
            "maxItems": 6,
            "items": {
              "$ref": "#/components/schemas/pages_ResumoIndicador"
            }
          },
          "series": {
            "type": "array",
            "maxItems": 2,
            "items": {
              "$ref": "#/components/schemas/pages_ResumoSerie"
            }
          }
        }
      },
      "pages_ResumoIndicador": {
        "type": "object",
        "required": [
          "chave",
          "rotulo",
          "valor",
          "anterior",
          "formato",
          "link"
        ],
        "properties": {
          "chave": {
            "type": "string",
            "description": "id estável do indicador"
          },
          "rotulo": {
            "type": "string",
            "description": "rótulo pt-BR (o front pode traduzir pela chave)"
          },
          "valor": {
            "type": "number"
          },
          "anterior": {
            "type": [
              "number",
              "null"
            ],
            "description": "mesmo indicador na janela anterior de mesmo tamanho; null = sem comparação"
          },
          "formato": {
            "type": "string",
            "enum": [
              "numero",
              "percentual",
              "moeda",
              "duracao"
            ],
            "description": "moeda em centavos BRL; duracao em segundos; percentual 0–100"
          },
          "link": {
            "type": "string",
            "description": "caminho relativo dentro da ferramenta, já com a conta"
          }
        }
      },
      "pages_ResumoSerie": {
        "type": "object",
        "required": [
          "chave",
          "rotulo",
          "tipo",
          "pontos"
        ],
        "properties": {
          "chave": {
            "type": "string"
          },
          "rotulo": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "linha",
              "barra"
            ]
          },
          "pontos": {
            "type": "array",
            "description": "um ponto por dia da janela (dia sem evento vem com 0)",
            "items": {
              "type": "object",
              "required": [
                "data",
                "valor"
              ],
              "properties": {
                "data": {
                  "type": "string",
                  "format": "date"
                },
                "valor": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "pages_RecorteFunil": {
        "type": "object",
        "properties": {
          "chave": {
            "type": "string",
            "description": "utm_source, host do referrer ou (direto); dispositivo; ou host+caminho da página"
          },
          "visualizacoes": {
            "type": "integer"
          },
          "inicios": {
            "type": "integer"
          },
          "envios": {
            "type": "integer"
          },
          "taxa_conversao": {
            "type": "number"
          }
        }
      },
      "pages_ErroAutoria": {
        "type": "object",
        "description": "Envelope de erro da AUTORIA (asender_pages_api).",
        "required": [
          "erro"
        ],
        "properties": {
          "erro": {
            "type": "string",
            "description": "mensagem legível; nunca revela existência de recurso de outra conta"
          },
          "codigo": {
            "type": "string",
            "description": "código estável, quando a recusa tem um (ex.: destinos integracao)"
          },
          "local": {
            "type": "string",
            "description": "o campo culpado, quando há (ex.: destinos[1].config.status_id)"
          }
        }
      },
      "pages_ErroRuntime": {
        "type": "object",
        "description": "Envelope de erro do RUNTIME (asender_pages_runtime) — RFC 7807-compatível.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message"
            ],
            "properties": {
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "pages_PaginaArquivada": {
        "type": "object",
        "description": "Uma página na lixeira do usuário.",
        "required": [
          "id",
          "titulo",
          "slug",
          "dominio",
          "arquivada_em",
          "arquivada_por",
          "arquivada_por_nome"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "titulo": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "dominio": {
            "type": "string",
            "description": "Vazio = host da plataforma."
          },
          "arquivada_em": {
            "type": "string",
            "format": "date-time"
          },
          "arquivada_por": {
            "type": "string",
            "description": "Id do usuário que arquivou; vazio quando foi o sistema."
          },
          "arquivada_por_nome": {
            "type": "string",
            "description": "Nome (senão e-mail) de quem arquivou, resolvido no servidor pelo lote do auth; vazio quando foi o sistema ou o nome não pôde ser resolvido (auth fora). Telas mostram este campo, nunca o id."
          }
        }
      },
      "pages_FormularioArquivado": {
        "type": "object",
        "description": "Um formulário na lixeira do usuário.",
        "required": [
          "id",
          "nome",
          "arquivado_em",
          "arquivado_por"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "public_id do formulário."
          },
          "nome": {
            "type": "string"
          },
          "arquivado_em": {
            "type": "string",
            "format": "date-time"
          },
          "arquivado_por": {
            "type": "string",
            "description": "Id do usuário que apagou (lido da auditoria); vazio quando foi chave de API, o sistema ou o rastro não existe."
          },
          "arquivado_por_nome": {
            "type": "string",
            "description": "Nome (senão e-mail) de quem apagou, resolvido no servidor; opcional — vazio quando o nome não pôde ser resolvido. Telas mostram este campo, nunca o id."
          }
        }
      },
      "pages_Pagina": {
        "type": "object",
        "description": "Uma página do construtor.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "slug": {
            "type": "string",
            "description": "caminho da página no host (ex.: `promo`)"
          },
          "dominio": {
            "type": "string",
            "description": "host onde ela é servida; vazio usa o padrão `sites.asender.net`"
          },
          "titulo": {
            "type": "string"
          },
          "template": {
            "$ref": "#/components/schemas/pages_TemplatePagina"
          }
        }
      },
      "pages_PedidoLote": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "acao",
          "ids"
        ],
        "properties": {
          "acao": {
            "type": "string",
            "enum": [
              "publicar",
              "despublicar",
              "duplicar",
              "mudar_dominio",
              "arquivar"
            ]
          },
          "ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "string"
            }
          },
          "rascunho": {
            "type": "string",
            "enum": [
              "pular",
              "publicar_salva"
            ],
            "default": "pular",
            "description": "só vale em publicar"
          },
          "simular": {
            "type": "boolean",
            "default": false
          },
          "dominio_id": {
            "type": "string",
            "description": "mudar_dominio: domínio de destino (\"\" = host da plataforma)"
          },
          "caminho_por_pagina": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "mudar_dominio: id da página → caminho novo (ausente = mantém)"
          }
        }
      },
      "pages_ResultadoLote": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "titulo": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "pulada",
              "erro"
            ]
          },
          "motivo": {
            "type": "string",
            "enum": [
              "nao_encontrada",
              "rascunho_mais_novo",
              "conteudo_invalido",
              "sem_versao",
              "ja_publicada",
              "ja_despublicada",
              "sem_privilegio",
              "slug_em_uso",
              "dominio_invalido",
              "caminho_em_uso",
              "indisponivel",
              "falha_interna"
            ]
          },
          "detalhe": {
            "type": "string"
          },
          "sem_mudanca": {
            "type": "boolean"
          },
          "versao": {
            "type": "integer"
          },
          "rascunho_mais_novo": {
            "type": "boolean"
          },
          "nova_pagina_id": {
            "type": "string"
          },
          "novo_slug": {
            "type": "string"
          },
          "url_destino": {
            "type": "string"
          }
        }
      },
      "pages_RespostaLote": {
        "type": "object",
        "required": [
          "lote_id",
          "acao",
          "simulado",
          "total",
          "ok",
          "puladas",
          "erros",
          "resultados"
        ],
        "properties": {
          "lote_id": {
            "type": "string"
          },
          "acao": {
            "type": "string"
          },
          "simulado": {
            "type": "boolean"
          },
          "total": {
            "type": "integer"
          },
          "ok": {
            "type": "integer"
          },
          "puladas": {
            "type": "integer"
          },
          "erros": {
            "type": "integer"
          },
          "resultados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_ResultadoLote"
            }
          }
        }
      },
      "pages_ImportarPagina": {
        "type": "object",
        "description": "Corpo de `POST /v1/paginas/importar` (a conta vem da sessão; `tenant_id` é recusado).",
        "required": [
          "slug",
          "titulo",
          "template"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "dominio": {
            "type": "string",
            "description": "domínio próprio; exige posse PROVADA do host"
          },
          "titulo": {
            "type": "string"
          },
          "template": {
            "$ref": "#/components/schemas/pages_TemplatePagina"
          },
          "seo": {
            "type": "object",
            "description": "SEO da página vindo do arquivo; `og_imagem_url` também é copiada para a conta.",
            "additionalProperties": true
          }
        }
      },
      "pages_ItemImportacaoMidia": {
        "type": "object",
        "required": [
          "original",
          "estado",
          "usos"
        ],
        "properties": {
          "original": {
            "type": "string"
          },
          "novo": {
            "type": "string",
            "description": "URL na biblioteca da conta (vazio se mantido)"
          },
          "estado": {
            "type": "string",
            "enum": [
              "copiado",
              "ja_na_conta",
              "mantido_original"
            ]
          },
          "motivo": {
            "type": "string",
            "description": "só em mantido_original: destino_bloqueado, redirecionamentos_demais, endereco_nao_resolvido, arquivo_grande, tipo_nao_permitido, svg_recusado, antivirus_recusou, antivirus_indisponivel, falha_ao_baixar, falha_ao_gravar, tempo_esgotado, limite_total_excedido, limite_de_arquivos, importacao_externa_desligada, midia_origem_inexistente, caminho_relativo"
          },
          "midia_id": {
            "type": "string"
          },
          "usos": {
            "type": "integer"
          },
          "caminhos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "pages_AvisoImportacao": {
        "type": "object",
        "required": [
          "codigo"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "enum": [
              "formulario_removido",
              "rastreio_removido",
              "vinculo_removido",
              "seo_nao_salvo"
            ]
          },
          "caminho": {
            "type": "string"
          },
          "detalhe": {
            "type": "string"
          }
        }
      },
      "pages_RelatorioImportacao": {
        "type": "object",
        "required": [
          "itens",
          "avisos",
          "copiadas",
          "ja_na_conta",
          "mantidas",
          "copia_disponivel",
          "seo_aplicado"
        ],
        "properties": {
          "itens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_ItemImportacaoMidia"
            }
          },
          "avisos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_AvisoImportacao"
            }
          },
          "copiadas": {
            "type": "integer"
          },
          "ja_na_conta": {
            "type": "integer"
          },
          "mantidas": {
            "type": "integer"
          },
          "copia_disponivel": {
            "type": "boolean",
            "description": "false = o serviço subiu sem galeria; nada foi copiado"
          },
          "seo_aplicado": {
            "type": "boolean"
          }
        }
      },
      "pages_ResultadoImportacao": {
        "type": "object",
        "required": [
          "pagina",
          "importacao"
        ],
        "properties": {
          "pagina": {
            "$ref": "#/components/schemas/pages_Pagina"
          },
          "importacao": {
            "$ref": "#/components/schemas/pages_RelatorioImportacao"
          }
        }
      },
      "pages_ResultadoRecopia": {
        "type": "object",
        "required": [
          "importacao"
        ],
        "properties": {
          "importacao": {
            "$ref": "#/components/schemas/pages_RelatorioImportacao"
          },
          "versao": {
            "type": "integer",
            "description": "presente quando uma versão nova foi gravada"
          }
        }
      },
      "pages_CriarPagina": {
        "type": "object",
        "description": "Corpo de `POST /v1/paginas`.",
        "required": [
          "titulo"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "dominio": {
            "type": "string",
            "description": "domínio próprio; exige posse PROVADA do host (senão 422)"
          },
          "titulo": {
            "type": "string"
          },
          "template": {
            "allOf": [
              {
                "$ref": "#/components/schemas/pages_TemplatePagina"
              }
            ],
            "description": "conteúdo inicial (v1 ou v2); conteúdo privilegiado (bloco/item `html`, HTML cru) exige nível pelo PEP. Ausente: a página nasce v2 com UMA seção (preset `hero`) com o título digitado — sem cabeçalho forçado."
          }
        }
      },
      "pages_EditarPagina": {
        "type": "object",
        "description": "Corpo de `PATCH /v1/paginas/{id}`. Campo ausente não muda; ao menos um é obrigatório.",
        "properties": {
          "slug": {
            "type": "string"
          },
          "dominio": {
            "type": "string"
          },
          "titulo": {
            "type": "string"
          }
        }
      },
      "pages_TemplatePagina": {
        "description": "O conteúdo de uma versão da página. A escrita aceita os DOIS formatos durante a transição (ADR-0086): o número `versao` escolhe o contrato. `1` (ou ausente) é a lista plana de blocos; `2` é o documento seção > colunas > itens, de estrutura, estilo e props FECHADOS (campo desconhecido = 422). A leitura devolve o que foi gravado (versões v1 antigas seguem v1).",
        "oneOf": [
          {
            "$ref": "#/components/schemas/pages_TemplateV1"
          },
          {
            "$ref": "#/components/schemas/pages_DocumentoV2"
          }
        ]
      },
      "pages_TemplateV1": {
        "type": "object",
        "description": "Template v1: blocos `{tipo, ...campos}` (passthrough por campo; tipo da allowlist).",
        "required": [
          "secoes"
        ],
        "properties": {
          "versao": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "configuracoes": {
            "type": "object"
          },
          "secoes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "object",
              "required": [
                "tipo"
              ],
              "properties": {
                "tipo": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "pages_ErroConteudo": {
        "type": "object",
        "description": "Recusa (422) de um documento v2. `codigo` é o nome da sentinela do validador (o mesmo do validador TS do editor) e `caminho` aponta o nó culpado — o editor seleciona o nó por ele. Sem nó, `caminho` é `versao`, `secoes` ou `documento`. Recusa de template v1 continua só com `erro`.",
        "required": [
          "erro",
          "codigo",
          "caminho"
        ],
        "properties": {
          "erro": {
            "type": "string",
            "description": "mensagem legível (já cita o caminho)"
          },
          "codigo": {
            "type": "string",
            "enum": [
              "ErrMalformado",
              "ErrVersao",
              "ErrSemSecoes",
              "ErrLimite",
              "ErrSemColunas",
              "ErrID",
              "ErrAncora",
              "ErrTipoDesconhecido",
              "ErrPropInvalida",
              "ErrEstiloInvalido",
              "ErrEstruturaInvalida"
            ]
          },
          "caminho": {
            "type": "string",
            "examples": [
              "secoes[1].colunas[0].itens[2].props.cor"
            ]
          }
        }
      },
      "pages_DocumentoV2": {
        "title": "Documento de página v2 (seção > colunas > itens)",
        "description": "Contrato do template de página v2 do Asender Pages. Fonte Go: asender_pages_api/internal/domain/documento (cópia idêntica no asender_pages_runtime); gêmeo TS: asender_pages_front/lib/documento-v2. TestEsquemaJSONBateComGo e o teste esquema-v2 do front provam que os três concordam. Plano: asender_archive/plan/2026-10-03-pages-secoes-colunas-itens.md.",
        "x-limites": {
          "MaxSecoes": 200,
          "MaxColunas": 12,
          "MaxPesoColuna": 12,
          "MaxItensPorColuna": 100,
          "MaxItensTotal": 1000,
          "MaxItensLista": 50,
          "MaxEspaco": 400,
          "MaxRaio": 200,
          "MaxBorda": 20,
          "MinLarguraMax": 320,
          "MaxLarguraMax": 2400,
          "MaxAlturaPx": 2000,
          "MaxAlturaVh": 100,
          "MaxAtrasoPopup": 120,
          "GapPadraoItens": 16,
          "GapPadraoColunas": 24,
          "LarguraMaxPadrao": 1080,
          "MaxTamanhoID": 64,
          "MaxTamanhoNome": 80,
          "MaxTamanhoURL": 2048,
          "MaxTextoCurto": 300,
          "MaxTextoLongo": 20000,
          "MaxTamanhoHTML": 200000,
          "MaxTamanhoPrazo": 64,
          "MaxTextoTag": 60,
          "MaxPaineis": 20,
          "MaxItensPorPainel": 20,
          "MaxTituloPainel": 200,
          "MaxIconeTag": 48,
          "MaxFAQResposta": 4000,
          "MaxFAQPagina": 50,
          "MaxSubir": 200,
          "MaxPaddingImagem": 40,
          "MinIconeFundo": 24,
          "MaxIconeFundo": 72,
          "MaxColunasCartoes": 4,
          "MinLarguraCartao": 96,
          "MaxLarguraCartao": 400,
          "MinMiniatura": 56,
          "MaxMiniatura": 96,
          "MaxInclinar": 12,
          "MaxRotacaoCartao": 15
        },
        "x-privilegiados": [
          "html"
        ],
        "x-observacoes": [
          "Unicidade de id (seção, coluna e item no MESMO espaço) e de âncora, o teto de 1000 itens por página e as regras de fundo por tipo são conferidos pelo validador (Go/TS), não por este schema.",
          "Toda prop urlLink aceita a irmã <prop>_rastreio (objeto do encurtador, link.TrocarPorCurtos).",
          "ADR-0109 — abas/acordeao: os painéis moram em props.itens; cada painel tem titulo e itens aninhados (um nível só: nunca abas, acordeao, html nem formulario dentro). Ids dos itens aninhados dividem o espaço de ids do documento; o teto de 1000 itens os conta. aberta < nº de abas; acordeao com um_por_vez aceita no máximo um painel aberto; faq exige texto de resposta (até MaxFAQResposta) em cada painel e até MaxFAQPagina perguntas na página. Abas não geram schema.org; acordeao com faq=true gera FAQPage no JSON-LD da página publicada.",
          "L3 — estilo.subir (0..MaxSubir px) vira margin-top negativa e SUBSTITUI a margem de topo do item.",
          "L4 — fundo.cor_base é a cor sólida sob as demais camadas; fundo.sobreposicao exige `cor` OU `gradiente` (um só), com opacidade 0..100 na camada inteira; desenha-se sobre imagem, gradiente ou cor.",
          "L5 — imagem: `raio` PRESENTE vence `forma`; ausente = a forma de sempre.",
          "L6 — icone_fundo*/icone_dir_fundo*: contêiner (círculo/quadrado) atrás do ícone do início/fim; `redes` só no item. links.efeito_icone=girar gira o ícone do fim no hover.",
          "L2 — cartoes: até MaxItensLista cartões; permitido dentro de painel; `rotacao` do cartão vence `inclinar` do conjunto; `cor_fim` liga o degradê do fundo do cartão.",
          "Texto vale em code points (runas), não em bytes nem em unidades UTF-16."
        ],
        "type": "object",
        "additionalProperties": false,
        "required": [
          "versao",
          "secoes"
        ],
        "properties": {
          "versao": {
            "const": 2
          },
          "configuracoes": {},
          "estilo": {
            "$ref": "#/components/schemas/pages_DocV2_estiloPagina"
          },
          "secoes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/pages_DocV2_secao"
            }
          }
        }
      },
      "pages_DocV2_id": {
        "type": "string",
        "pattern": "^[A-Za-z0-9_-]{1,64}$"
      },
      "pages_DocV2_ancora": {
        "type": "string",
        "pattern": "^[a-z][a-z0-9-]{0,63}$"
      },
      "pages_DocV2_cor": {
        "type": "string",
        "anyOf": [
          {
            "pattern": "^#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
          },
          {
            "enum": [
              "transparente",
              "acento",
              "texto",
              "suave",
              "fundo",
              "superficie",
              "borda"
            ]
          }
        ]
      },
      "pages_DocV2_corOuVazia": {
        "anyOf": [
          {
            "const": ""
          },
          {
            "$ref": "#/components/schemas/pages_DocV2_cor"
          }
        ]
      },
      "pages_DocV2_urlLink": {
        "type": "string",
        "maxLength": 2048,
        "description": "Vazia, #ancora, /caminho (não //) ou esquema http/https/mailto/tel; sem caractere de controle."
      },
      "pages_DocV2_urlMidia": {
        "type": "string",
        "maxLength": 2048,
        "pattern": "^[^\"'()\\\\ <>\\u0000-\\u001f\\u007f]*$",
        "description": "Vazia, /caminho (não //) ou http(s) com host; sem aspas, parênteses, barra invertida, espaço, < > nem controle."
      },
      "pages_DocV2_identOuVazio": {
        "type": "string",
        "pattern": "^([a-z0-9_-]{1,40})?$"
      },
      "pages_DocV2_idRefOuVazio": {
        "type": "string",
        "pattern": "^([A-Za-z0-9_-]{1,64})?$"
      },
      "pages_DocV2_rastreio": {
        "type": [
          "object",
          "null"
        ],
        "description": "Objeto do encurtador (link_id, codigo, destino, desligado…); passthrough."
      },
      "pages_DocV2_lados": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "topo": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "direita": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "base": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "esquerda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          }
        }
      },
      "pages_DocV2_fundo": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "nenhum",
              "cor",
              "gradiente",
              "imagem"
            ]
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_base": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "gradiente": {
            "$ref": "#/components/schemas/pages_DocV2_gradiente"
          },
          "imagem": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "url"
            ],
            "properties": {
              "url": {
                "$ref": "#/components/schemas/pages_DocV2_urlMidia"
              },
              "posicao": {
                "type": "string",
                "enum": [
                  "centro",
                  "topo",
                  "base",
                  "esquerda",
                  "direita",
                  "topo-esquerda",
                  "topo-direita",
                  "base-esquerda",
                  "base-direita"
                ]
              },
              "tamanho": {
                "type": "string",
                "enum": [
                  "cobrir",
                  "conter",
                  "auto"
                ]
              },
              "repetir": {
                "type": "boolean"
              },
              "fixo": {
                "type": "boolean"
              }
            }
          },
          "sobreposicao": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "opacidade"
            ],
            "oneOf": [
              {
                "required": [
                  "cor"
                ]
              },
              {
                "required": [
                  "gradiente"
                ]
              }
            ],
            "properties": {
              "cor": {
                "$ref": "#/components/schemas/pages_DocV2_cor"
              },
              "gradiente": {
                "$ref": "#/components/schemas/pages_DocV2_gradiente"
              },
              "opacidade": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              }
            }
          }
        }
      },
      "pages_DocV2_gradiente": {
        "description": "Degradê de duas cores (e uma do meio, opcional): o do fundo e o da sobreposição. As cores aceitam o token `transparente` (fade).",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "de",
          "para"
        ],
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "linear",
              "radial"
            ]
          },
          "de": {
            "$ref": "#/components/schemas/pages_DocV2_cor"
          },
          "meio": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "para": {
            "$ref": "#/components/schemas/pages_DocV2_cor"
          },
          "angulo": {
            "type": "integer",
            "minimum": 0,
            "maximum": 360
          }
        }
      },
      "pages_DocV2_borda": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "largura": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "estilo": {
            "type": "string",
            "enum": [
              "solida",
              "tracejada",
              "pontilhada"
            ]
          },
          "lados": {
            "type": "array",
            "uniqueItems": true,
            "items": {
              "enum": [
                "topo",
                "direita",
                "base",
                "esquerda"
              ]
            }
          }
        }
      },
      "pages_DocV2_estiloPagina": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tema": {
            "type": "string",
            "enum": [
              "claro",
              "escuro",
              "auto"
            ]
          },
          "fundo": {
            "$ref": "#/components/schemas/pages_DocV2_fundo"
          },
          "padrao": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "tipo"
            ],
            "properties": {
              "tipo": {
                "type": "string",
                "enum": [
                  "nenhum",
                  "pontos",
                  "grade",
                  "diagonal"
                ]
              },
              "cor": {
                "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
              },
              "opacidade": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "tamanho": {
                "anyOf": [
                  {
                    "const": 0
                  },
                  {
                    "type": "integer",
                    "minimum": 8,
                    "maximum": 96
                  }
                ]
              }
            }
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "fonte_titulos": {
            "description": "Família (títulos h1..h6): vazio/ausente herda do kit de marca; senão um id da allowlist (auto-hospedada em /_f/). Qualquer outro valor é recusado (ErrEstiloInvalido).",
            "anyOf": [
              {
                "const": ""
              },
              {
                "type": "string",
                "enum": [
                  "comfortaa",
                  "urbanist",
                  "montserrat",
                  "inter",
                  "poppins",
                  "open-sans",
                  "nunito",
                  "playfair-display",
                  "merriweather",
                  "dm-sans",
                  "manrope",
                  "raleway"
                ]
              }
            ]
          },
          "fonte_texto": {
            "description": "Família (texto corrido): vazio/ausente herda do kit de marca; senão um id da allowlist (auto-hospedada em /_f/). Qualquer outro valor é recusado (ErrEstiloInvalido).",
            "anyOf": [
              {
                "const": ""
              },
              {
                "type": "string",
                "enum": [
                  "comfortaa",
                  "urbanist",
                  "montserrat",
                  "inter",
                  "poppins",
                  "open-sans",
                  "nunito",
                  "playfair-display",
                  "merriweather",
                  "dm-sans",
                  "manrope",
                  "raleway"
                ]
              }
            ]
          },
          "preloader": {
            "description": "Tela de carregamento com a logo do kit de marca efetivo. `logo` é uma variante do kit, nunca uma URL.",
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "ativo": {
                "type": "boolean"
              },
              "logo": {
                "type": "string",
                "enum": [
                  "principal",
                  "horizontal",
                  "icone",
                  "escuro"
                ]
              },
              "animacao": {
                "type": "string",
                "enum": [
                  "pulso",
                  "fade",
                  "giro",
                  "barra"
                ]
              },
              "cor_fundo": {
                "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
              },
              "tamanho": {
                "anyOf": [
                  {
                    "const": 0
                  },
                  {
                    "type": "integer",
                    "minimum": 32,
                    "maximum": 240
                  }
                ]
              },
              "duracao_min_ms": {
                "type": "integer",
                "minimum": 0,
                "maximum": 3000
              }
            }
          },
          "conteudo": {
            "$ref": "#/components/schemas/pages_DocV2_estiloCaixa"
          }
        }
      },
      "pages_DocV2_estiloCaixa": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "padding": {
            "$ref": "#/components/schemas/pages_DocV2_lados"
          },
          "padding_celular": {
            "$ref": "#/components/schemas/pages_DocV2_lados"
          },
          "fundo": {
            "$ref": "#/components/schemas/pages_DocV2_fundo"
          },
          "borda": {
            "$ref": "#/components/schemas/pages_DocV2_borda"
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "sombra": {
            "type": "string",
            "enum": [
              "nenhuma",
              "suave",
              "media",
              "forte"
            ]
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          }
        }
      },
      "pages_DocV2_estiloSecao": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "padding": {
            "$ref": "#/components/schemas/pages_DocV2_lados"
          },
          "padding_celular": {
            "$ref": "#/components/schemas/pages_DocV2_lados"
          },
          "fundo": {
            "$ref": "#/components/schemas/pages_DocV2_fundo"
          },
          "borda": {
            "$ref": "#/components/schemas/pages_DocV2_borda"
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "sombra": {
            "type": "string",
            "enum": [
              "nenhuma",
              "suave",
              "media",
              "forte"
            ]
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "largura": {
            "type": "string",
            "enum": [
              "contida",
              "total"
            ]
          },
          "largura_max": {
            "type": "integer",
            "anyOf": [
              {
                "const": 0
              },
              {
                "minimum": 320,
                "maximum": 2400
              }
            ]
          },
          "altura_minima": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2000
          },
          "altura_unidade": {
            "type": "string",
            "enum": [
              "px",
              "vh"
            ]
          },
          "alinhamento_vertical": {
            "type": "string",
            "enum": [
              "topo",
              "centro",
              "base",
              "esticar"
            ]
          },
          "gap_colunas": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          }
        }
      },
      "pages_DocV2_secao": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "colunas"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/pages_DocV2_id"
          },
          "preset": {
            "type": "string",
            "pattern": "^[a-z0-9_-]{1,40}$"
          },
          "nome": {
            "type": "string",
            "maxLength": 80
          },
          "ancora": {
            "$ref": "#/components/schemas/pages_DocV2_ancora"
          },
          "comportamento": {
            "type": "string",
            "enum": [
              "normal",
              "fixa_topo",
              "popup"
            ]
          },
          "atraso_popup": {
            "type": "integer",
            "minimum": 0,
            "maximum": 120
          },
          "ocultar_em": {
            "type": "array",
            "uniqueItems": true,
            "items": {
              "enum": [
                "computador",
                "tablet",
                "celular"
              ]
            }
          },
          "ocultar_se_vazia": {
            "type": "boolean"
          },
          "layout_celular": {
            "type": "string",
            "enum": [
              "empilhar",
              "manter"
            ]
          },
          "inverter_celular": {
            "type": "boolean"
          },
          "estilo": {
            "$ref": "#/components/schemas/pages_DocV2_estiloSecao"
          },
          "colunas": {
            "type": "array",
            "minItems": 1,
            "maxItems": 12,
            "items": {
              "$ref": "#/components/schemas/pages_DocV2_coluna"
            }
          }
        }
      },
      "pages_DocV2_coluna": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "itens"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/pages_DocV2_id"
          },
          "largura": {
            "type": "integer",
            "minimum": 0,
            "maximum": 12
          },
          "alinhamento_vertical": {
            "type": "string",
            "enum": [
              "topo",
              "centro",
              "base",
              "esticar"
            ]
          },
          "alinhamento_horizontal": {
            "type": "string",
            "enum": [
              "inicio",
              "centro",
              "fim",
              "esticar"
            ]
          },
          "gap": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "estilo": {
            "$ref": "#/components/schemas/pages_DocV2_estiloCaixa"
          },
          "itens": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/pages_DocV2_item"
            }
          }
        }
      },
      "pages_DocV2_item": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "tipo",
          "props"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/pages_DocV2_id"
          },
          "tipo": {
            "enum": [
              "titulo",
              "texto",
              "imagem",
              "botao",
              "tags",
              "links",
              "cartoes",
              "abas",
              "acordeao",
              "redes",
              "menu",
              "formulario",
              "video",
              "divisor",
              "espacador",
              "icone",
              "selo",
              "contador",
              "marca",
              "html"
            ]
          },
          "props": {
            "type": "object"
          },
          "estilo": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "margem": {
                "$ref": "#/components/schemas/pages_DocV2_lados"
              },
              "alinhamento": {
                "type": "string",
                "enum": [
                  "herdar",
                  "inicio",
                  "centro",
                  "fim"
                ]
              },
              "largura_max": {
                "type": "integer",
                "minimum": 0,
                "maximum": 2400
              },
              "cor_texto": {
                "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
              },
              "subir": {
                "type": "integer",
                "minimum": 0,
                "maximum": 200
              }
            }
          },
          "ocultar_em": {
            "type": "array",
            "uniqueItems": true,
            "items": {
              "enum": [
                "computador",
                "tablet",
                "celular"
              ]
            }
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "titulo"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_titulo"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "texto"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_texto"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "imagem"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_imagem"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "botao"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_botao"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "tags"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_tags"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "links"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_links"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "cartoes"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_cartoes"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "abas"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_abas"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "acordeao"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_acordeao"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "redes"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_redes"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "menu"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_menu"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "formulario"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_formulario"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "video"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_video"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "divisor"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_divisor"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "espacador"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_espacador"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "icone"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_icone"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "selo"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_selo"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "contador"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_contador"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "marca"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_marca"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "tipo": {
                  "const": "html"
                }
              }
            },
            "then": {
              "properties": {
                "props": {
                  "$ref": "#/components/schemas/pages_DocV2_props_html"
                }
              }
            }
          }
        ]
      },
      "pages_DocV2_props_titulo": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "texto": {
            "type": "string",
            "maxLength": 300
          },
          "nivel": {
            "type": "integer",
            "minimum": 1,
            "maximum": 6
          },
          "tamanho": {
            "type": "string",
            "enum": [
              "auto",
              "p",
              "m",
              "g",
              "gg"
            ]
          },
          "transformar": {
            "type": "string",
            "enum": [
              "nenhum",
              "caixa_alta",
              "caixa_baixa",
              "capitalizar"
            ]
          },
          "espacamento": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6
          },
          "destaque_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          }
        }
      },
      "pages_DocV2_props_texto": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "texto": {
            "type": "string",
            "maxLength": 20000
          },
          "variante": {
            "type": "string",
            "enum": [
              "normal",
              "suave",
              "destaque",
              "citacao"
            ]
          },
          "tamanho": {
            "type": "string",
            "enum": [
              "auto",
              "p",
              "m",
              "g",
              "gg"
            ]
          },
          "transformar": {
            "type": "string",
            "enum": [
              "nenhum",
              "caixa_alta",
              "caixa_baixa",
              "capitalizar"
            ]
          },
          "espacamento": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6
          },
          "destaque_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          }
        }
      },
      "pages_DocV2_props_imagem": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "url": {
            "$ref": "#/components/schemas/pages_DocV2_urlMidia"
          },
          "alt": {
            "type": "string",
            "maxLength": 300
          },
          "link": {
            "$ref": "#/components/schemas/pages_DocV2_urlLink"
          },
          "forma": {
            "type": "string",
            "enum": [
              "normal",
              "arredondada",
              "circulo"
            ]
          },
          "largura": {
            "type": "integer",
            "minimum": 16,
            "maximum": 2400
          },
          "proporcao": {
            "type": "string",
            "enum": [
              "auto",
              "1:1",
              "4:3",
              "3:4",
              "16:9",
              "9:16"
            ]
          },
          "ajuste": {
            "type": "string",
            "enum": [
              "cobrir",
              "conter"
            ]
          },
          "borda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "sombra": {
            "type": "string",
            "enum": [
              "nenhuma",
              "leve",
              "forte"
            ]
          },
          "cor_fundo": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "padding": {
            "type": "integer",
            "minimum": 0,
            "maximum": 40
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "link_rastreio": {
            "$ref": "#/components/schemas/pages_DocV2_rastreio"
          }
        }
      },
      "pages_DocV2_props_botao": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "rotulo": {
            "type": "string",
            "maxLength": 300
          },
          "transformar": {
            "type": "string",
            "enum": [
              "nenhum",
              "caixa_alta",
              "caixa_baixa",
              "capitalizar"
            ]
          },
          "espacamento": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6
          },
          "url": {
            "$ref": "#/components/schemas/pages_DocV2_urlLink"
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "variante": {
            "type": "string",
            "enum": [
              "solido",
              "contorno",
              "texto"
            ]
          },
          "tamanho": {
            "type": "string",
            "enum": [
              "p",
              "m",
              "g"
            ]
          },
          "largura_total": {
            "type": "boolean"
          },
          "nova_aba": {
            "type": "boolean"
          },
          "cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "borda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "padding_v": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "padding_h": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "fonte_peso": {
            "type": "string",
            "enum": [
              "400",
              "500",
              "600",
              "700",
              "800"
            ]
          },
          "fonte_tamanho": {
            "type": "integer",
            "minimum": 10,
            "maximum": 72
          },
          "sombra": {
            "type": "string",
            "enum": [
              "nenhuma",
              "leve",
              "forte"
            ]
          },
          "icone": {
            "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
          },
          "icone_dir": {
            "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
          },
          "hover_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "vidro": {
            "type": "boolean"
          },
          "vidro_opacidade": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "vidro_desfoque": {
            "type": "integer",
            "minimum": 0,
            "maximum": 40
          },
          "vidro_saturacao": {
            "type": "integer",
            "minimum": 100,
            "maximum": 200
          },
          "icone_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          },
          "icone_dir_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_dir_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_dir_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          },
          "cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_icone_inicio": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_icone_fim": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "imagem": {
            "$ref": "#/components/schemas/pages_DocV2_urlMidia"
          },
          "imagem_alt": {
            "type": "string",
            "maxLength": 300
          },
          "imagem_posicao": {
            "type": "string",
            "enum": [
              "inicio",
              "fim",
              "topo"
            ]
          },
          "imagem_tamanho": {
            "type": "string",
            "enum": [
              "p",
              "m",
              "g"
            ]
          },
          "imagem_forma": {
            "type": "string",
            "enum": [
              "quadrada",
              "arredondada",
              "circulo"
            ]
          },
          "icone_img": {
            "$ref": "#/components/schemas/pages_DocV2_urlMidia"
          },
          "icone_dir_img": {
            "$ref": "#/components/schemas/pages_DocV2_urlMidia"
          },
          "url_rastreio": {
            "$ref": "#/components/schemas/pages_DocV2_rastreio"
          }
        }
      },
      "pages_DocV2_props_links": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itens": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "titulo": {
                  "type": "string",
                  "maxLength": 300
                },
                "texto": {
                  "type": "string",
                  "maxLength": 300
                },
                "url": {
                  "$ref": "#/components/schemas/pages_DocV2_urlLink"
                },
                "icone": {
                  "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
                },
                "icone_img": {
                  "$ref": "#/components/schemas/pages_DocV2_urlMidia"
                },
                "icone_dir": {
                  "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
                },
                "icone_dir_img": {
                  "$ref": "#/components/schemas/pages_DocV2_urlMidia"
                },
                "destaque": {
                  "type": "boolean"
                },
                "nova_aba": {
                  "type": "boolean"
                },
                "cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "cor_texto": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "cor_borda": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_texto": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_borda": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "raio": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 200
                },
                "borda": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 20
                },
                "padding_v": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 400
                },
                "padding_h": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 400
                },
                "fonte_peso": {
                  "type": "string",
                  "enum": [
                    "400",
                    "500",
                    "600",
                    "700",
                    "800"
                  ]
                },
                "fonte_tamanho": {
                  "type": "integer",
                  "minimum": 10,
                  "maximum": 72
                },
                "sombra": {
                  "type": "string",
                  "enum": [
                    "nenhuma",
                    "leve",
                    "forte"
                  ]
                },
                "vidro": {
                  "type": "boolean"
                },
                "vidro_opacidade": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100
                },
                "vidro_desfoque": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 40
                },
                "vidro_saturacao": {
                  "type": "integer",
                  "minimum": 100,
                  "maximum": 200
                },
                "icone_fundo": {
                  "type": "string",
                  "enum": [
                    "nenhum",
                    "circulo",
                    "quadrado"
                  ]
                },
                "icone_fundo_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "icone_fundo_tamanho": {
                  "type": "integer",
                  "minimum": 24,
                  "maximum": 72
                },
                "icone_dir_fundo": {
                  "type": "string",
                  "enum": [
                    "nenhum",
                    "circulo",
                    "quadrado"
                  ]
                },
                "icone_dir_fundo_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "icone_dir_fundo_tamanho": {
                  "type": "integer",
                  "minimum": 24,
                  "maximum": 72
                },
                "cor_icone": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "cor_icone_inicio": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "cor_icone_fim": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_icone": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "imagem": {
                  "$ref": "#/components/schemas/pages_DocV2_urlMidia"
                },
                "imagem_alt": {
                  "type": "string",
                  "maxLength": 300
                },
                "imagem_posicao": {
                  "type": "string",
                  "enum": [
                    "inicio",
                    "fim",
                    "topo"
                  ]
                },
                "imagem_tamanho": {
                  "type": "string",
                  "enum": [
                    "p",
                    "m",
                    "g"
                  ]
                },
                "imagem_forma": {
                  "type": "string",
                  "enum": [
                    "quadrada",
                    "arredondada",
                    "circulo"
                  ]
                },
                "url_rastreio": {
                  "$ref": "#/components/schemas/pages_DocV2_rastreio"
                }
              }
            }
          },
          "estilo": {
            "type": "string",
            "enum": [
              "cartao",
              "preenchido",
              "contorno",
              "fantasma",
              "suave",
              "sombra"
            ]
          },
          "tamanho": {
            "type": "string",
            "enum": [
              "p",
              "m",
              "g"
            ]
          },
          "largura": {
            "type": "string",
            "enum": [
              "total",
              "conteudo"
            ]
          },
          "alinhamento_texto": {
            "type": "string",
            "enum": [
              "centro",
              "inicio"
            ]
          },
          "efeito": {
            "type": "string",
            "enum": [
              "elevar",
              "escala",
              "brilho",
              "fundo",
              "sublinhado",
              "nenhum"
            ]
          },
          "efeito_icone": {
            "type": "string",
            "enum": [
              "nenhum",
              "deslizar",
              "destacar",
              "girar"
            ]
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "borda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "espaco": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "vidro": {
            "type": "boolean"
          },
          "vidro_opacidade": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "vidro_desfoque": {
            "type": "integer",
            "minimum": 0,
            "maximum": 40
          },
          "vidro_saturacao": {
            "type": "integer",
            "minimum": 100,
            "maximum": 200
          },
          "icone_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          },
          "icone_dir_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_dir_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_dir_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          },
          "cor_icone_inicio": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_icone_fim": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "tamanho_icone": {
            "type": "integer",
            "minimum": 12,
            "maximum": 64
          }
        }
      },
      "pages_DocV2_props_tags": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itens": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "texto": {
                  "type": "string",
                  "maxLength": 60
                },
                "icone": {
                  "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
                },
                "url": {
                  "$ref": "#/components/schemas/pages_DocV2_urlLink"
                },
                "nova_aba": {
                  "type": "boolean"
                },
                "cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "cor_texto": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "cor_borda": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_texto": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_borda": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "raio": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 200
                },
                "borda": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 20
                },
                "padding_v": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 400
                },
                "padding_h": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 400
                },
                "fonte_peso": {
                  "type": "string",
                  "enum": [
                    "400",
                    "500",
                    "600",
                    "700",
                    "800"
                  ]
                },
                "fonte_tamanho": {
                  "type": "integer",
                  "minimum": 10,
                  "maximum": 72
                },
                "sombra": {
                  "type": "string",
                  "enum": [
                    "nenhuma",
                    "leve",
                    "forte"
                  ]
                },
                "vidro": {
                  "type": "boolean"
                },
                "vidro_opacidade": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100
                },
                "vidro_desfoque": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 40
                },
                "vidro_saturacao": {
                  "type": "integer",
                  "minimum": 100,
                  "maximum": 200
                },
                "icone_fundo": {
                  "type": "string",
                  "enum": [
                    "nenhum",
                    "circulo",
                    "quadrado"
                  ]
                },
                "icone_fundo_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "icone_fundo_tamanho": {
                  "type": "integer",
                  "minimum": 24,
                  "maximum": 72
                },
                "icone_dir_fundo": {
                  "type": "string",
                  "enum": [
                    "nenhum",
                    "circulo",
                    "quadrado"
                  ]
                },
                "icone_dir_fundo_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "icone_dir_fundo_tamanho": {
                  "type": "integer",
                  "minimum": 24,
                  "maximum": 72
                },
                "cor_icone": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_icone": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "imagem": {
                  "$ref": "#/components/schemas/pages_DocV2_urlMidia"
                },
                "imagem_alt": {
                  "type": "string",
                  "maxLength": 300
                },
                "imagem_posicao": {
                  "type": "string",
                  "enum": [
                    "inicio",
                    "fim",
                    "topo"
                  ]
                },
                "imagem_tamanho": {
                  "type": "string",
                  "enum": [
                    "p",
                    "m",
                    "g"
                  ]
                },
                "imagem_forma": {
                  "type": "string",
                  "enum": [
                    "quadrada",
                    "arredondada",
                    "circulo"
                  ]
                },
                "icone_img": {
                  "$ref": "#/components/schemas/pages_DocV2_urlMidia"
                },
                "icone_dir_img": {
                  "$ref": "#/components/schemas/pages_DocV2_urlMidia"
                },
                "cor_icone_inicio": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "url_rastreio": {
                  "$ref": "#/components/schemas/pages_DocV2_rastreio"
                }
              }
            }
          },
          "preset": {
            "type": "string",
            "enum": [
              "translucido",
              "solido",
              "contorno"
            ]
          },
          "icone": {
            "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
          },
          "cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "vidro": {
            "type": "boolean"
          },
          "vidro_opacidade": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "vidro_desfoque": {
            "type": "integer",
            "minimum": 0,
            "maximum": 40
          },
          "vidro_saturacao": {
            "type": "integer",
            "minimum": 100,
            "maximum": 200
          },
          "icone_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          },
          "icone_dir_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_dir_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_dir_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          },
          "hover_cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "tamanho_icone": {
            "type": "integer",
            "minimum": 10,
            "maximum": 48
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "borda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "padding_v": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "padding_h": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "fonte_peso": {
            "type": "string",
            "enum": [
              "400",
              "500",
              "600",
              "700",
              "800"
            ]
          },
          "fonte_tamanho": {
            "type": "integer",
            "minimum": 10,
            "maximum": 72
          },
          "gap": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "alinhamento": {
            "type": "string",
            "enum": [
              "inicio",
              "centro",
              "fim"
            ]
          },
          "quebra": {
            "type": "string",
            "enum": [
              "quebrar",
              "linha"
            ]
          }
        }
      },
      "pages_DocV2_props_cartoes": {
        "description": "Galeria de cartões (L2): foto + título/texto/tag/ícone + link, em grade, carrossel ou lista. Tudo opcional; ausente = o desenho padrão.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itens": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "img": {
                  "$ref": "#/components/schemas/pages_DocV2_urlMidia"
                },
                "alt": {
                  "type": "string",
                  "maxLength": 300
                },
                "titulo": {
                  "type": "string",
                  "maxLength": 300
                },
                "texto": {
                  "type": "string",
                  "maxLength": 300
                },
                "tag": {
                  "type": "string",
                  "maxLength": 60
                },
                "icone": {
                  "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
                },
                "url": {
                  "$ref": "#/components/schemas/pages_DocV2_urlLink"
                },
                "nova_aba": {
                  "type": "boolean"
                },
                "rotacao": {
                  "type": "integer",
                  "minimum": -15,
                  "maximum": 15
                },
                "cor_fim": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "angulo": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 360
                },
                "cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "cor_texto": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "cor_borda": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_texto": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_borda": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "raio": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 200
                },
                "borda": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 20
                },
                "padding_v": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 400
                },
                "padding_h": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 400
                },
                "fonte_peso": {
                  "type": "string",
                  "enum": [
                    "400",
                    "500",
                    "600",
                    "700",
                    "800"
                  ]
                },
                "fonte_tamanho": {
                  "type": "integer",
                  "minimum": 10,
                  "maximum": 72
                },
                "sombra": {
                  "type": "string",
                  "enum": [
                    "nenhuma",
                    "leve",
                    "forte"
                  ]
                },
                "cor_icone": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "hover_cor_icone": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "vidro": {
                  "type": "boolean"
                },
                "vidro_opacidade": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100
                },
                "vidro_desfoque": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 40
                },
                "vidro_saturacao": {
                  "type": "integer",
                  "minimum": 100,
                  "maximum": 200
                },
                "icone_fundo": {
                  "type": "string",
                  "enum": [
                    "nenhum",
                    "circulo",
                    "quadrado"
                  ]
                },
                "icone_fundo_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "icone_fundo_tamanho": {
                  "type": "integer",
                  "minimum": 24,
                  "maximum": 72
                },
                "icone_dir_fundo": {
                  "type": "string",
                  "enum": [
                    "nenhum",
                    "circulo",
                    "quadrado"
                  ]
                },
                "icone_dir_fundo_cor": {
                  "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
                },
                "icone_dir_fundo_tamanho": {
                  "type": "integer",
                  "minimum": 24,
                  "maximum": 72
                },
                "url_rastreio": {
                  "$ref": "#/components/schemas/pages_DocV2_rastreio"
                }
              }
            }
          },
          "layout": {
            "type": "string",
            "enum": [
              "grade",
              "carrossel",
              "lista"
            ]
          },
          "colunas": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4
          },
          "largura_cartao": {
            "type": "integer",
            "minimum": 96,
            "maximum": 400
          },
          "proporcao": {
            "type": "string",
            "enum": [
              "auto",
              "1:1",
              "4:3",
              "3:4",
              "16:9",
              "9:16",
              "4:5"
            ]
          },
          "ajuste": {
            "type": "string",
            "enum": [
              "cobrir",
              "conter"
            ]
          },
          "legenda": {
            "type": "string",
            "enum": [
              "abaixo",
              "sobre",
              "nenhuma"
            ]
          },
          "miniatura": {
            "type": "integer",
            "minimum": 56,
            "maximum": 96
          },
          "forma_miniatura": {
            "type": "string",
            "enum": [
              "quadrada",
              "arredondada",
              "circulo"
            ]
          },
          "moldura": {
            "type": "string",
            "enum": [
              "nenhuma",
              "polaroid"
            ]
          },
          "inclinar": {
            "type": "integer",
            "minimum": 0,
            "maximum": 12
          },
          "rotulo": {
            "type": "string",
            "maxLength": 300
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_sup_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_dir": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "raio_inf_esq": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "borda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "sombra": {
            "type": "string",
            "enum": [
              "nenhuma",
              "leve",
              "forte"
            ]
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "gap": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "efeito": {
            "type": "string",
            "enum": [
              "elevar",
              "escala",
              "brilho",
              "nenhum"
            ]
          },
          "alinhamento_texto": {
            "type": "string",
            "enum": [
              "centro",
              "inicio"
            ]
          },
          "tamanho_icone": {
            "type": "integer",
            "minimum": 12,
            "maximum": 64
          },
          "cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "vidro": {
            "type": "boolean"
          },
          "vidro_opacidade": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "vidro_desfoque": {
            "type": "integer",
            "minimum": 0,
            "maximum": 40
          },
          "vidro_saturacao": {
            "type": "integer",
            "minimum": 100,
            "maximum": 200
          },
          "icone_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          },
          "icone_dir_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_dir_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_dir_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          }
        }
      },
      "pages_DocV2_props_abas": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itens": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "titulo": {
                  "type": "string",
                  "maxLength": 200
                },
                "itens": {
                  "$ref": "#/components/schemas/pages_DocV2_itensDePainel"
                }
              }
            }
          },
          "aberta": {
            "type": "integer",
            "minimum": 0,
            "maximum": 19
          },
          "rotulo": {
            "type": "string",
            "maxLength": 300
          },
          "estilo": {
            "type": "string",
            "enum": [
              "linha",
              "pilula",
              "cartao"
            ]
          },
          "alinhamento": {
            "type": "string",
            "enum": [
              "inicio",
              "centro",
              "fim",
              "esticar"
            ]
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_ativa": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_texto_ativa": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_borda_ativa": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "borda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "padding_v": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "padding_h": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "fonte_peso": {
            "type": "string",
            "enum": [
              "400",
              "500",
              "600",
              "700",
              "800"
            ]
          },
          "fonte_tamanho": {
            "type": "integer",
            "minimum": 10,
            "maximum": 72
          },
          "gap": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "painel_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "painel_cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "painel_cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "painel_borda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "painel_raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "painel_padding": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          }
        }
      },
      "pages_DocV2_props_acordeao": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itens": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "titulo": {
                  "type": "string",
                  "maxLength": 200
                },
                "aberto": {
                  "type": "boolean"
                },
                "itens": {
                  "$ref": "#/components/schemas/pages_DocV2_itensDePainel"
                }
              }
            }
          },
          "um_por_vez": {
            "type": "boolean"
          },
          "faq": {
            "type": "boolean"
          },
          "nivel": {
            "type": "integer",
            "minimum": 2,
            "maximum": 6
          },
          "estilo": {
            "type": "string",
            "enum": [
              "cartao",
              "linha",
              "simples"
            ]
          },
          "indicador": {
            "type": "string",
            "enum": [
              "seta",
              "mais",
              "nenhum"
            ]
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_borda": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_aberto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "cor_texto_aberto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "hover_cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 200
          },
          "borda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 20
          },
          "padding_v": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "padding_h": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "fonte_peso": {
            "type": "string",
            "enum": [
              "400",
              "500",
              "600",
              "700",
              "800"
            ]
          },
          "fonte_tamanho": {
            "type": "integer",
            "minimum": 10,
            "maximum": 72
          },
          "gap": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "painel_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "painel_cor_texto": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "painel_padding": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          }
        }
      },
      "pages_DocV2_itensDePainel": {
        "description": "Itens aninhados de um painel de abas/acordeão (ADR-0109): objetos `item` dos tipos permitidos em painel (nunca abas, acordeao, html nem formulario); `props` obrigatório. A conferência de id único e de props de cada item é a do `item`.",
        "type": "array",
        "maxItems": 20,
        "items": {
          "$ref": "#/components/schemas/pages_DocV2_item",
          "required": [
            "id",
            "tipo",
            "props"
          ],
          "properties": {
            "tipo": {
              "enum": [
                "titulo",
                "texto",
                "imagem",
                "botao",
                "tags",
                "links",
                "cartoes",
                "redes",
                "menu",
                "video",
                "divisor",
                "espacador",
                "icone",
                "selo",
                "contador",
                "marca"
              ]
            }
          }
        }
      },
      "pages_DocV2_props_redes": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itens": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "rede": {
                  "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
                },
                "url": {
                  "$ref": "#/components/schemas/pages_DocV2_urlLink"
                },
                "url_rastreio": {
                  "$ref": "#/components/schemas/pages_DocV2_rastreio"
                }
              }
            }
          },
          "tamanho": {
            "type": "string",
            "enum": [
              "p",
              "m",
              "g"
            ]
          },
          "cor_icone": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          },
          "icone_dir_fundo": {
            "type": "string",
            "enum": [
              "nenhum",
              "circulo",
              "quadrado"
            ]
          },
          "icone_dir_fundo_cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "icone_dir_fundo_tamanho": {
            "type": "integer",
            "minimum": 24,
            "maximum": 72
          }
        }
      },
      "pages_DocV2_props_menu": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itens": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "rotulo": {
                  "type": "string",
                  "maxLength": 300
                },
                "url": {
                  "$ref": "#/components/schemas/pages_DocV2_urlLink"
                },
                "url_rastreio": {
                  "$ref": "#/components/schemas/pages_DocV2_rastreio"
                }
              }
            }
          },
          "orientacao": {
            "type": "string",
            "enum": [
              "horizontal",
              "vertical"
            ]
          },
          "colapsar_celular": {
            "type": "boolean"
          }
        }
      },
      "pages_DocV2_props_formulario": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "form_id": {
            "$ref": "#/components/schemas/pages_DocV2_idRefOuVazio"
          },
          "rotulo": {
            "type": "string",
            "maxLength": 300
          }
        }
      },
      "pages_DocV2_props_video": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "url": {
            "$ref": "#/components/schemas/pages_DocV2_urlLink",
            "description": "Embed: sem irmã url_rastreio (o encurtador trocaria o src por um 302)."
          },
          "titulo": {
            "type": "string",
            "maxLength": 300
          }
        }
      },
      "pages_DocV2_props_divisor": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          },
          "espessura": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "estilo": {
            "type": "string",
            "enum": [
              "solida",
              "tracejada",
              "pontilhada"
            ]
          },
          "largura": {
            "type": "integer",
            "minimum": 10,
            "maximum": 100
          }
        }
      },
      "pages_DocV2_props_espacador": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "altura": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          },
          "altura_celular": {
            "type": "integer",
            "minimum": 0,
            "maximum": 400
          }
        }
      },
      "pages_DocV2_props_icone": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "nome": {
            "$ref": "#/components/schemas/pages_DocV2_identOuVazio"
          },
          "tamanho": {
            "type": "integer",
            "minimum": 12,
            "maximum": 256
          },
          "cor": {
            "$ref": "#/components/schemas/pages_DocV2_corOuVazia"
          }
        }
      },
      "pages_DocV2_props_selo": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "texto": {
            "type": "string",
            "maxLength": 300
          }
        }
      },
      "pages_DocV2_props_contador": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "prazo": {
            "type": "string",
            "maxLength": 64
          },
          "rotulo": {
            "type": "string",
            "maxLength": 300
          }
        }
      },
      "pages_DocV2_props_marca": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mostrar_logo": {
            "type": "boolean"
          },
          "mostrar_nome": {
            "type": "boolean"
          },
          "altura_logo": {
            "type": "integer",
            "minimum": 16,
            "maximum": 200
          }
        }
      },
      "pages_DocV2_props_html": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "html": {
            "type": "string",
            "maxLength": 200000
          }
        }
      },
      "pages_Form": {
        "type": "object",
        "description": "Definição de um formulário — o contrato de campos que a captura valida.",
        "required": [
          "nome",
          "campos"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Id do formulário no SEU sistema (ex.: o id do formulário do blog). Único por conta entre formulários não apagados (colisão → 409). Na resposta vem sempre que existir. No `PUT /v1/formularios/{id}`, ausente = mantém o atual; presente e diferente = troca, se livre.",
            "example": "blog-contato"
          },
          "nome": {
            "type": "string"
          },
          "campos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_Campo"
            }
          },
          "exige_consentimento": {
            "type": "boolean"
          },
          "texto_consentimento": {
            "type": "string",
            "maxLength": 500,
            "description": "rótulo da caixa de aceite mostrado ao titular; vazio assume o padrão (\"Concordo em receber comunicações\")"
          },
          "base_legal": {
            "type": "string",
            "default": "consentimento",
            "enum": [
              "consentimento",
              "legitimo_interesse",
              "execucao_contrato",
              "obrigacao_legal"
            ],
            "description": "base legal de tratamento (LGPD, Art. 7); default `consentimento`"
          },
          "origens": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "allowlist de origens do modo snippet (CORS por FORMULÁRIO)"
          },
          "destino_pos_envio": {
            "type": "string",
            "description": "URL para onde levar o visitante depois de enviar"
          },
          "mensagem_pos_envio": {
            "type": "string",
            "description": "texto da tela de agradecimento padrão"
          },
          "restringir_origens": {
            "type": "boolean",
            "description": "liga a trava de sites autorizados (exige ao menos uma origem)"
          },
          "texto_botao": {
            "type": "string",
            "maxLength": 60,
            "description": "texto do botão de envio; vazio = \"Enviar\""
          },
          "layout": {
            "$ref": "#/components/schemas/pages_LayoutFormulario"
          },
          "estilo_botao": {
            "$ref": "#/components/schemas/pages_EstiloBotaoFormulario"
          },
          "janela": {
            "$ref": "#/components/schemas/pages_JanelaFormulario"
          },
          "acoes_agendadas": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/pages_AcaoAgendada"
            },
            "description": "Ações pós-envio AGENDADAS (até 10): depois do envio, cada ação cuja condição passou sai com atraso ou numa data fixa. O `PUT` substitui o recurso — omitir remove as anteriores. Ação sem `id` ganha um `aag_*` gerado; violações respondem 422 com o campo `acoes_agendadas`. A fila e a execução são do runtime (ADR-0150);"
          }
        }
      },
      "pages_AcaoAgendada": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "canal",
          "quando"
        ],
        "description": "Uma ação pós-envio agendada. Canais (conjunto FECHADO):\n- `email`, `whatsapp`, `sms`: exigem `para` (nome de um campo do formulário,\n do tipo `email` no e-mail e `telefone` no WhatsApp/SMS) e `template`;\n `variaveis` mapeia variável do template para nome de campo.\n- `webhook`, `integracao`: exigem `destino_id` de um destino já declarado do\n MESMO tipo no formulário (webhook: `wh_` + 12 hex do SHA-256 da URL;\n integração: id da instalação `itg_*`); não aceitam `para`, `template` nem `variaveis`.\n`exige_consentimento` assume `true` nos canais de mensagem quando omitido e\nnão pode ser `false` enquanto a `base_legal` do formulário for `consentimento`.\nA condição usa o mesmo formato da dos destinos.",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^aag_[a-z0-9]{8,32}$",
            "description": "identificador da ação, único no formulário; omitido na entrada, o servidor gera"
          },
          "canal": {
            "type": "string",
            "enum": [
              "email",
              "whatsapp",
              "sms",
              "webhook",
              "integracao"
            ]
          },
          "quando": {
            "$ref": "#/components/schemas/pages_QuandoAcao"
          },
          "condicao": {
            "allOf": [
              {
                "$ref": "#/components/schemas/pages_CondicaoDestino"
              }
            ],
            "description": "QUANDO a ação é enfileirada, julgada no envio; ausente = sempre. Mesmo formato e validação da condição dos destinos"
          },
          "para": {
            "type": "string",
            "description": "nome do campo do formulário com o destinatário (email ou telefone); só nos canais de mensagem"
          },
          "template": {
            "type": "string",
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9_.:-]+$",
            "description": "id do template no canal; obrigatório nos canais de mensagem"
          },
          "variaveis": {
            "type": "object",
            "maxProperties": 20,
            "additionalProperties": {
              "type": "string"
            },
            "description": "variável do template (chave [A-Za-z0-9_]) para nome de campo declarado do formulário"
          },
          "destino_id": {
            "type": "string",
            "description": "webhook/integracao: id de um destino já declarado do mesmo tipo (ver descrição do schema)"
          },
          "exige_consentimento": {
            "type": "boolean",
            "description": "exige o consentimento do titular antes de enviar; omitido vale true em email/whatsapp/sms e false nos demais. A resposta sempre traz o valor"
          }
        }
      },
      "pages_QuandoAcao": {
        "type": "object",
        "description": "Quando a ação sai: informe EXATAMENTE um entre `atraso_min` (relativo ao envio)\ne `em` (data fixa). `em` deve ser futuro no momento do salvamento (a ação\nidêntica já gravada é tolerada) e é normalizado para UTC ao segundo. Ação que\npassa da tolerância de execução (2h) é pulada, nunca disparada atrasada.",
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "atraso_min"
            ],
            "properties": {
              "atraso_min": {
                "type": "integer",
                "minimum": 1,
                "maximum": 525600,
                "description": "minutos depois do envio (1 a 525600, um ano)"
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "em"
            ],
            "properties": {
              "em": {
                "type": "string",
                "format": "date-time",
                "description": "data fixa (RFC 3339, UTC); deve ser futura"
              }
            }
          }
        ]
      },
      "pages_JanelaFormulario": {
        "type": "object",
        "additionalProperties": false,
        "description": "Janela de funcionamento DESTE formulário: quando abre, quando fecha e quantas\nrespostas aceita. Todo campo é opcional; formulário sem `janela` (ou com `{}`)\nfica sempre aberto, como antes. O `PUT` substitui o recurso — omitir o objeto\nremove uma janela anterior. Horários em UTC (RFC 3339), normalizados ao segundo.\nAbertura inclusiva (no instante `abre_em` já está aberto) e fechamento exclusivo\n(no instante `fecha_em` já está encerrado). Fora da janela o runtime responde\n410 Gone (RFC 9457) e mostra a mensagem correspondente. `fecha_em` deve ser\nposterior a `abre_em`; violações respondem 422 com o campo `janela`.",
        "properties": {
          "abre_em": {
            "type": "string",
            "format": "date-time",
            "description": "instante em que o formulário passa a aceitar respostas; ausente = já aberto"
          },
          "fecha_em": {
            "type": "string",
            "format": "date-time",
            "description": "instante em que o formulário deixa de aceitar respostas; ausente = sem fim. Deve ser posterior a abre_em"
          },
          "max_respostas": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000,
            "description": "teto de respostas; atingido, o formulário encerra por limite. Ausente = sem teto"
          },
          "mensagem_fechado": {
            "type": "string",
            "maxLength": 500,
            "description": "texto mostrado ao visitante quando o formulário está encerrado (data ou limite); texto simples, escapado na renderização"
          },
          "mensagem_antes": {
            "type": "string",
            "maxLength": 500,
            "description": "texto mostrado ao visitante antes da abertura; texto simples, escapado na renderização"
          }
        }
      },
      "pages_EstiloBotaoFormulario": {
        "type": "object",
        "additionalProperties": false,
        "description": "Personalização do botão de envio DESTE formulário, por cima do kit de marca (`--asf-cor`, `--asf-raio`). Todo campo é opcional e ausente HERDA o kit/padrão: formulário sem `estilo_botao` (ou com `{}`) renderiza exatamente como antes. O `PUT` substitui o recurso — omitir o objeto limpa uma personalização anterior. Cores só `#rrggbb` (`#rgb` é aceito e normalizado para 6 dígitos minúsculos); qualquer valor fora do domínio responde 422 com o campo culpado, sem ecoar o valor. O servidor valida só o DOMÍNIO — o contraste texto x fundo (< 4.5:1) é aviso do editor e não bloqueia o salvar. `gradienteAte` só vale com `estilo: gradiente` e `alinhamento` só com `largura: auto`; fora disso são aceitos e ignorados. No snippet publicado vira um bloco `<style>` escopado ao formulário (sem `style=\"\"` inline).",
        "properties": {
          "estilo": {
            "type": "string",
            "enum": [
              "solido",
              "contorno",
              "fantasma",
              "gradiente"
            ]
          },
          "cor": {
            "type": "string",
            "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
            "description": "fundo/cor principal"
          },
          "corTexto": {
            "type": "string",
            "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
          },
          "corBorda": {
            "type": "string",
            "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
          },
          "larguraBorda": {
            "type": "integer",
            "minimum": 0,
            "maximum": 8,
            "description": "px"
          },
          "gradienteAte": {
            "type": "string",
            "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
            "description": "2ª cor; só estilo gradiente (ausente deriva da principal)"
          },
          "raio": {
            "type": "integer",
            "minimum": 0,
            "maximum": 40,
            "description": "px; 0 = quadrado"
          },
          "altura": {
            "type": "string",
            "enum": [
              "p",
              "m",
              "g"
            ],
            "description": "36 / 44 / 52 px"
          },
          "largura": {
            "type": "string",
            "enum": [
              "total",
              "auto"
            ]
          },
          "alinhamento": {
            "type": "string",
            "enum": [
              "esquerda",
              "centro",
              "direita"
            ],
            "description": "só vale com largura auto (padrão esquerda)"
          },
          "peso": {
            "type": "string",
            "enum": [
              "normal",
              "semibold",
              "bold"
            ]
          },
          "sombra": {
            "type": "boolean"
          },
          "textoMaiusculo": {
            "type": "boolean"
          }
        }
      },
      "pages_LayoutFormulario": {
        "type": "object",
        "description": "Disposição dos campos em linhas de 1 a 4 colunas iguais (schema v2). Só POSICIONA — a validação continua sendo a de `campos`. Ausente = legado: uma coluna por linha na ordem de `campos`. Na escrita, todo campo aparece exatamente uma vez e nenhum nome desconhecido é aceito (422). Linha sem campo é descartada; coluna vazia é permitida (espaço reservado). O PUT substitui o recurso: omitir o layout volta ao legado.",
        "required": [
          "versao",
          "linhas"
        ],
        "properties": {
          "versao": {
            "type": "integer",
            "enum": [
              2
            ]
          },
          "linhas": {
            "type": "array",
            "maxItems": 60,
            "items": {
              "type": "object",
              "required": [
                "colunas"
              ],
              "properties": {
                "colunas": {
                  "type": "array",
                  "minItems": 1,
                  "maxItems": 4,
                  "items": {
                    "type": "object",
                    "required": [
                      "campos"
                    ],
                    "properties": {
                      "campos": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "nomes dos campos, empilhados"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "example": {
          "versao": 2,
          "linhas": [
            {
              "colunas": [
                {
                  "campos": [
                    "nome"
                  ]
                },
                {
                  "campos": [
                    "sobrenome"
                  ]
                }
              ]
            },
            {
              "colunas": [
                {
                  "campos": [
                    "email"
                  ]
                }
              ]
            }
          ]
        }
      },
      "pages_FormDetalhe": {
        "type": "object",
        "description": "O formulário publicado, os embeds prontos e o rascunho pendente do editor.",
        "properties": {
          "formulario": {
            "$ref": "#/components/schemas/pages_Form"
          },
          "snippet": {
            "type": "string",
            "description": "HTML completo do embed"
          },
          "snippet_js": {
            "type": "string",
            "description": "linha de <script> que carrega o embed atual"
          },
          "endpoint": {
            "type": "string"
          },
          "endpoint_api": {
            "type": "string"
          },
          "rascunho": {
            "type": "object",
            "nullable": true,
            "description": "corpo do PUT salvo sem publicar (`null` = nada pendente)"
          },
          "rascunho_em": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "pages_Campo": {
        "type": "object",
        "description": "Um campo declarado do formulário. Submissão de campo não declarado é RECUSADA.",
        "required": [
          "nome",
          "tipo"
        ],
        "properties": {
          "nome": {
            "type": "string",
            "description": "nome técnico (chave da submissão)"
          },
          "chave": {
            "type": "string",
            "description": "chave do catálogo de campos nativos (ex.: `empresa`, `cargo`) — liga o campo ao atributo correspondente do contato no CRM"
          },
          "rotulo": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "texto",
              "texto_longo",
              "email",
              "telefone",
              "numero",
              "booleano",
              "opcao",
              "data",
              "cpf",
              "cnpj",
              "cep",
              "url",
              "pais",
              "oculto",
              "endereco",
              "agendamento"
            ]
          },
          "obrigatorio": {
            "type": "boolean"
          },
          "opcoes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "valores aceitos quando `tipo=opcao`"
          },
          "marcado_padrao": {
            "type": "boolean",
            "description": "caixa de seleção (`booleano`) já marcada; padrão false; recusado na caixa de consentimento (LGPD)"
          },
          "valor_marcado": {
            "type": "string",
            "maxLength": 100,
            "description": "caixa de seleção: valor enviado quando marcada (vazio = \"true\"); não pode ser uma forma de \"não\""
          },
          "consentimento": {
            "type": "boolean",
            "description": "Esta caixa de seleção É o consentimento de comunicações (opt-in do contato): não vira atributo do lead, vira o `consentimento` do evento com o texto da caixa e a `base_legal` do formulário. No máximo uma por formulário. O rótulo de uma caixa aceita link no formato `[texto](https://…)`. Formulário antigo com `exige_consentimento` é LIDO com esta caixa (nome `consentimento`) no fim dos campos."
          },
          "endereco": {
            "$ref": "#/components/schemas/pages_ConfigEndereco"
          },
          "agendamento": {
            "$ref": "#/components/schemas/pages_ConfigAgendamento"
          }
        }
      },
      "pages_ConfigAgendamento": {
        "type": "object",
        "description": "Configuração do campo de AGENDAMENTO (só no `tipo=agendamento`; obrigatória nele, recusada nos outros tipos). O visitante escolhe um horário livre da agenda; o valor submetido é o INÍCIO do horário, instante RFC 3339 normalizado para UTC com precisão de segundo (`2026-12-01T13:00:00Z`) — o runtime reserva o horário na submissão. No máximo UM campo de agendamento por formulário; não alimenta chave do catálogo nativo; aceita `condicao`. Ao salvar, a agenda é conferida no serviço Agenda (deve existir na conta e estar ativa: 422 `agenda-inexistente`/`agenda-inativa`; serviço fora do ar: 503 `agenda-indisponivel`, só para formulário COM o campo).",
        "required": [
          "agenda_id"
        ],
        "properties": {
          "agenda_id": {
            "type": "string",
            "pattern": "^agd_[0-9a-f]{16}$",
            "example": "agd_0123456789abcdef"
          }
        }
      },
      "pages_AgendaResumo": {
        "type": "object",
        "required": [
          "id",
          "nome",
          "fuso",
          "duracao_min",
          "ativa"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "agd_0123456789abcdef"
          },
          "nome": {
            "type": "string"
          },
          "fuso": {
            "type": "string",
            "description": "fuso IANA da agenda",
            "example": "America/Sao_Paulo"
          },
          "duracao_min": {
            "type": "integer"
          },
          "ativa": {
            "type": "boolean"
          }
        }
      },
      "pages_ConfigEndereco": {
        "type": "object",
        "description": "Configuração do widget de ENDEREÇO (só no `tipo=endereco`; mesma forma do `CampoEndereco` do @asender/ui). O campo se abre nas partes, sempre do mais abrangente ao mais específico: país > CEP > estado > cidade > bairro > rua > número + complemento. A submissão manda cada parte como `<nome>.<parte>` (`endereco.cep`…) e o lead grava as mesmas chaves, normalizadas: CEP no formato do país (BR `99999-999`), estado BR como UF (+ `<nome>.estado_nome`). Ausente = os padrões: BR editável, todos os países, CEP/cidade/rua/número obrigatórios. Uma parte do endereço vale como campo de condição do direcional (`endereco.estado = SP`).",
        "properties": {
          "pais": {
            "type": "object",
            "properties": {
              "oculto": {
                "type": "boolean",
                "description": "vai sempre o `padrao` (exige `padrao`)"
              },
              "padrao": {
                "type": "string",
                "minLength": 2,
                "maxLength": 2,
                "description": "ISO-2; ausente = BR, ou o primeiro permitido",
                "example": "BR"
              },
              "editavel": {
                "type": "boolean",
                "description": "false = o país aparece mas não se troca (ausente = true)"
              },
              "permitidos": {
                "type": "array",
                "maxItems": 300,
                "items": {
                  "type": "string",
                  "minLength": 2,
                  "maxLength": 2
                },
                "description": "países aceitos; vazio = todos"
              },
              "rotulo": {
                "type": "string",
                "maxLength": 100
              }
            }
          },
          "cep": {
            "$ref": "#/components/schemas/pages_ConfigParteEndereco"
          },
          "estado": {
            "$ref": "#/components/schemas/pages_ConfigParteEndereco"
          },
          "cidade": {
            "$ref": "#/components/schemas/pages_ConfigParteEndereco"
          },
          "bairro": {
            "$ref": "#/components/schemas/pages_ConfigParteEndereco"
          },
          "rua": {
            "$ref": "#/components/schemas/pages_ConfigParteEndereco"
          },
          "numero": {
            "$ref": "#/components/schemas/pages_ConfigParteEndereco"
          },
          "complemento": {
            "$ref": "#/components/schemas/pages_ConfigParteEndereco"
          }
        }
      },
      "pages_ConfigParteEndereco": {
        "type": "object",
        "properties": {
          "oculto": {
            "type": "boolean",
            "description": "a parte não aparece e não é coletada"
          },
          "obrigatorio": {
            "type": "boolean",
            "description": "ausente = o padrão da parte; oculta nunca é obrigatória"
          },
          "rotulo": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "pages_Destino": {
        "type": "object",
        "description": "Um destino de um formulário — para onde a submissão é copiada e como cada campo é mapeado. crm/marketing viajam no evento NATS v2; webhook é despachado localmente pelo runtime (com SSRF-guard, retry e HMAC); integracao vira o comando `integracoes.entrega.solicitada` (ADR-0099) para uma instalação do serviço de Integrações (API própria, Kommo) — a credencial mora lá, nunca aqui. Até 10 integrações por formulário, sem repetir instalação.",
        "required": [
          "tipo"
        ],
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "crm",
              "marketing",
              "webhook",
              "integracao"
            ]
          },
          "ativo": {
            "type": "boolean",
            "description": "destino desligado não dispara, mas guarda o mapa"
          },
          "config": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "webhook: { url, secret? }. marketing: { lista_id?, lista_nome? } (lista lst_…). crm: vazio. integracao: { instalacao_id (itg_…, obrigatório), + as opções por destino do catálogo (Kommo: pipeline_id, status_id, responsavel_id, tags separadas por vírgula) } — até 20 opções, valor ≤ 1024; opção vazia = a da instalação."
          },
          "mapa": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "enum": [
                "email",
                "telefone",
                "nome",
                ""
              ]
            },
            "description": "nome_do_campo → alvo canônico ou '' (custom, nunca descartado)"
          },
          "condicao": {
            "$ref": "#/components/schemas/pages_CondicaoDestino"
          },
          "condicao_erro": {
            "type": "string",
            "readOnly": true,
            "description": "Só na leitura: por que a condição gravada não vale mais (campo apagado do formulário, opção removida) ou \"Marketing sem a condição de aceite\". O runtime PULA o destino com esse motivo no trilho do lead — nunca o deixa passar."
          },
          "integracao": {
            "readOnly": true,
            "description": "Só na leitura, destino integracao: o resumo da instalação (do serviço de Integrações).",
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "app": {
                "type": "string",
                "enum": [
                  "http",
                  "kommo"
                ]
              },
              "app_nome": {
                "type": "string"
              },
              "nome": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "rascunho",
                  "ativa",
                  "pausada",
                  "reautorizar"
                ]
              }
            }
          },
          "integracao_erro": {
            "type": "string",
            "readOnly": true,
            "description": "Só na leitura, destino integracao: por que a instalação não recebe (removida / de outra conta, ou precisa ser reconectada). Ignorado na entrada."
          }
        }
      },
      "pages_CondicaoDestino": {
        "type": "object",
        "nullable": true,
        "description": "QUANDO o destino recebe a submissão (ausente/null = sempre). Avaliada no runtime, a cada submissão. O destino `marketing` só é aceito com uma condição que GARANTA a caixa de aceite marcada (modo `todas`, ou cláusula única) — é o que registra o opt-in; sem ela, 422.",
        "required": [
          "modo",
          "clausulas"
        ],
        "properties": {
          "modo": {
            "type": "string",
            "enum": [
              "todas",
              "qualquer"
            ]
          },
          "clausulas": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "object",
              "required": [
                "campo",
                "operador"
              ],
              "properties": {
                "campo": {
                  "type": "string",
                  "description": "nome de um campo do formulário, ou especial: @utm_source, @utm_medium, @utm_campaign, @utm_term, @utm_content, @pagina_url, @referrer"
                },
                "operador": {
                  "type": "string",
                  "description": "pelo tipo do campo — booleano: marcado|desmarcado; numero: maior|menor|igual; opcao: igual|um_de (valores entre as opções do campo); demais: igual|diferente| contem|vazio|preenchido|um_de",
                  "enum": [
                    "marcado",
                    "desmarcado",
                    "igual",
                    "diferente",
                    "contem",
                    "vazio",
                    "preenchido",
                    "um_de",
                    "maior",
                    "menor"
                  ]
                },
                "valor": {
                  "type": "string",
                  "maxLength": 500
                },
                "valores": {
                  "type": "array",
                  "maxItems": 50,
                  "items": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "example": {
          "modo": "todas",
          "clausulas": [
            {
              "campo": "aceite",
              "operador": "marcado"
            }
          ]
        }
      },
      "pages_PushConvite": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "modo": {
            "type": "string",
            "enum": [
              "barra",
              "botao_flutuante"
            ],
            "default": "barra"
          },
          "texto": {
            "type": "string",
            "minLength": 1,
            "maxLength": 140
          },
          "texto_botao": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "atraso_s": {
            "type": "integer",
            "minimum": 0,
            "maximum": 600,
            "default": 5
          }
        }
      },
      "pages_PushDoDominioEntrada": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ligado",
          "vapid_public_key",
          "site_key",
          "api_base"
        ],
        "properties": {
          "ligado": {
            "type": "boolean"
          },
          "vapid_public_key": {
            "type": "string",
            "description": "base64url",
            "minLength": 80,
            "maxLength": 100
          },
          "site_key": {
            "type": "string",
            "pattern": "^pk_push_[A-Za-z0-9._~-]{10,500}$"
          },
          "api_base": {
            "type": "string",
            "format": "uri",
            "description": "https",
            "sem caminho": null
          },
          "convite": {
            "$ref": "#/components/schemas/pages_PushConvite"
          }
        }
      },
      "pages_PushDoDominio": {
        "type": "object",
        "required": [
          "ligado"
        ],
        "properties": {
          "ligado": {
            "type": "boolean"
          },
          "vapid_public_key": {
            "type": "string"
          },
          "site_key": {
            "type": "string"
          },
          "api_base": {
            "type": "string"
          },
          "convite": {
            "$ref": "#/components/schemas/pages_PushConvite"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "pages_MoverDominioPedido": {
        "type": "object",
        "required": [
          "dominio_id"
        ],
        "properties": {
          "dominio_id": {
            "type": "string",
            "description": "id do domínio de destino (`GET /v1/dominios`); `\"\"` = host da plataforma",
            "examples": [
              "dom_01J9XYZ"
            ]
          },
          "caminho": {
            "type": "string",
            "pattern": "^([a-z0-9]+(-[a-z0-9]+)*)?$",
            "description": "slug de um segmento; vazio mantém o slug atual",
            "examples": [
              "meu-slug"
            ]
          }
        }
      },
      "pages_MoverDominioResposta": {
        "type": "object",
        "properties": {
          "pagina_id": {
            "type": "string"
          },
          "dominio_id": {
            "type": "string"
          },
          "dominio": {
            "type": "string",
            "description": "host de destino; vazio = host da plataforma"
          },
          "caminho": {
            "type": "string"
          },
          "url_publica": {
            "type": "string"
          },
          "alterado": {
            "type": "boolean",
            "description": "false quando a página já estava nesse endereço"
          },
          "publicada": {
            "type": "boolean"
          },
          "redirecionamento": {
            "type": [
              "object",
              "null"
            ],
            "description": "criado quando a página já foi publicada alguma vez e o endereço mudou; o endereço antigo passa a responder 301",
            "properties": {
              "criado": {
                "type": "boolean"
              },
              "de": {
                "type": "string",
                "examples": [
                  "https://antigo.exemplo.com/slug-antigo"
                ]
              }
            }
          }
        }
      },
      "pages_MoverDominioErro": {
        "type": "object",
        "properties": {
          "erro": {
            "type": "string"
          },
          "motivo": {
            "type": "string",
            "enum": [
              "caminho_em_uso",
              "pagina_inicial",
              "dominio_nao_verificado",
              "caminho_invalido"
            ]
          }
        }
      },
      "pages_Dominio": {
        "type": "object",
        "description": "Um domínio da conta, com os 3 passos da tela (plano 2026-10-02 §4.1): posse (TXT), apontamento (CNAME → nome-alvo no subdomínio; ALIAS/ flattening → nome-alvo no principal, com `www` e A/AAAA como caminhos B e C — migration 038) e certificado. `pronto` = os três ok. Os campos planos `registro_txt` e `verificado` seguem por compatibilidade.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "host": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "proprio",
              "gratis"
            ]
          },
          "classe": {
            "type": "string",
            "enum": [
              "principal",
              "subdominio"
            ],
            "description": "pela Public Suffix List"
          },
          "registro_txt": {
            "type": "string",
            "description": "o TXT de posse a publicar no DNS"
          },
          "verificado": {
            "type": "boolean"
          },
          "padrao": {
            "type": "boolean",
            "description": "true no domínio padrão da conta (`apages.xyz`, criado automaticamente); vem primeiro em `GET /v1/dominios`"
          },
          "posse": {
            "type": "object",
            "properties": {
              "ok": {
                "type": "boolean"
              },
              "registro_txt": {
                "type": "string"
              },
              "token": {
                "type": "string"
              },
              "verificado_em": {
                "type": "string",
                "format": "date-time"
              },
              "erro": {
                "type": "string"
              }
            }
          },
          "apontamento": {
            "type": "object",
            "properties": {
              "ok": {
                "type": "boolean"
              },
              "configurado": {
                "type": "boolean",
                "description": "false = o ambiente não tem alvo de apontamento"
              },
              "alvo_cname": {
                "type": "string",
                "examples": [
                  "cname.apages.xyz"
                ]
              },
              "esperado": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/pages_RegistroDNS"
                },
                "description": "o caminho recomendado, sempre por NOME: CNAME (subdomínio) ou ALIAS (principal) para `alvo_cname`; A/AAAA só sem nome-alvo configurado"
              },
              "alternativos": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/pages_RegistroDNS"
                },
                "description": "ÚLTIMO recurso do principal: A/AAAA da borda (os IPs podem mudar); vazio no subdomínio"
              },
              "www": {
                "type": "object",
                "description": "principal com nome-alvo: publicar em `www.<host>` por CNAME e redirecionar o principal para ele",
                "properties": {
                  "host": {
                    "type": "string",
                    "examples": [
                      "www.loja.com.br"
                    ]
                  },
                  "registros": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/pages_RegistroDNS"
                    }
                  }
                }
              },
              "encontrado": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/pages_RegistroDNS"
                },
                "description": "o que o DNS respondeu na última checagem"
              },
              "metodo": {
                "type": "string",
                "enum": [
                  "cname",
                  "enderecos"
                ],
                "description": "como o domínio chegou na última checagem; `enderecos` = mesmos IPs do nome-alvo (ALIAS/flattening ou A/AAAA)"
              },
              "diagnosticos": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/pages_DiagnosticoDNS"
                }
              },
              "recomenda_cname": {
                "type": "boolean",
                "description": "apontado por IP com nome-alvo disponível: recomendação NÃO bloqueante de migrar para CNAME/ALIAS"
              },
              "deriva_em": {
                "type": "string",
                "format": "date-time",
                "description": "quando o vigia viu o domínio apontado por IP deixar de casar com o nome-alvo (ausente = sem deriva); o domínio segue no ar"
              },
              "deriva": {
                "type": "string"
              },
              "checado_em": {
                "type": "string",
                "format": "date-time"
              },
              "erro": {
                "type": "string"
              }
            }
          },
          "tls": {
            "type": "object",
            "properties": {
              "estado": {
                "type": "string",
                "enum": [
                  "pendente",
                  "ativo",
                  "erro"
                ]
              },
              "expira_em": {
                "type": "string",
                "format": "date-time"
              },
              "erro": {
                "type": "string"
              }
            }
          },
          "pronto": {
            "type": "boolean"
          },
          "pagina_inicial": {
            "description": "A HOME (apex) do domínio — migration 029: qual página responde `GET https://host/`. `null` quando não há home marcada, ou quando a página marcada foi apagada ou saiu deste domínio.",
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/pages_PaginaInicial"
              }
            ]
          }
        }
      },
      "pages_PaginaInicial": {
        "type": "object",
        "description": "Resumo da página marcada como home de um domínio.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "titulo": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "publicada": {
            "type": "boolean",
            "description": "false = marcada como home mas ainda sem versão publicada"
          }
        }
      },
      "pages_DefinirPaginaInicial": {
        "type": "object",
        "required": [
          "pagina_id"
        ],
        "properties": {
          "pagina_id": {
            "type": "string",
            "format": "uuid",
            "description": "o id público da página da conta; se estiver em outro endereço, é movida para este domínio (mesmo slug)"
          }
        }
      },
      "pages_DiagnosticoDNS": {
        "type": "object",
        "description": "Uma observação precisa do passo 2 (a tela traduz pelo código).",
        "required": [
          "codigo"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "enum": [
              "cname_outro_alvo",
              "enderecos_outros",
              "enderecos_mistos",
              "proxy_cloudflare",
              "sem_registro",
              "ips_podem_mudar",
              "ttl",
              "deriva"
            ]
          },
          "encontrado": {
            "type": "string",
            "description": "o valor achado (nome ou IPs separados por vírgula)"
          },
          "esperado": {
            "type": "string",
            "description": "o valor esperado (nome-alvo ou IPs atuais dele)"
          },
          "segundos": {
            "type": "integer",
            "description": "o TTL, no código `ttl` (dica de propagação)"
          }
        }
      },
      "pages_RegistroDNS": {
        "type": "object",
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "TXT",
              "CNAME",
              "A",
              "AAAA",
              "ALIAS"
            ]
          },
          "nome": {
            "type": "string"
          },
          "valor": {
            "type": "string"
          }
        }
      },
      "pages_CriarDominio": {
        "type": "object",
        "required": [
          "host"
        ],
        "properties": {
          "host": {
            "type": "string",
            "examples": [
              "lp.empresa.com.br"
            ]
          }
        }
      },
      "pages_Redirect": {
        "type": "object",
        "description": "Um link curto.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "slug": {
            "type": "string"
          },
          "dominio": {
            "type": "string"
          },
          "destino": {
            "type": "string",
            "format": "uri"
          },
          "ativo": {
            "type": "boolean"
          },
          "cliques": {
            "type": "integer",
            "format": "int64",
            "readOnly": true
          }
        }
      },
      "pages_LinkConfigUTM": {
        "type": "object",
        "description": "Etiqueta de campanha que o link põe no destino. `modo` acrescentar = só se ausente; forcar = sobrescreve (e apaga repetições).",
        "properties": {
          "modo": {
            "type": "string",
            "enum": [
              "acrescentar",
              "forcar"
            ],
            "default": "acrescentar"
          },
          "source": {
            "type": "string",
            "maxLength": 200
          },
          "medium": {
            "type": "string",
            "maxLength": 200
          },
          "campaign": {
            "type": "string",
            "maxLength": 200
          },
          "term": {
            "type": "string",
            "maxLength": 200
          },
          "content": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "pages_LinkParametros": {
        "type": "object",
        "description": "Pares fixos (até 20) que o link sempre leva ao destino.",
        "properties": {
          "fixos": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 500
            }
          },
          "modo_fixos": {
            "type": "string",
            "enum": [
              "acrescentar",
              "forcar"
            ],
            "default": "acrescentar"
          }
        }
      },
      "pages_LinkRepasse": {
        "type": "object",
        "description": "O \"dispatcher\": o que fazer com os parâmetros que CHEGAM no link curto. Ordem: bloquear → lista/todos → renomear; o repassado nunca sobrescreve o destino. Nomes comparados sem caixa.",
        "properties": {
          "modo": {
            "type": "string",
            "enum": [
              "nenhum",
              "todos",
              "lista"
            ],
            "default": "nenhum"
          },
          "lista": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 50
          },
          "renomear": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "bloquear": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 50
          }
        }
      },
      "pages_LinkEntrada": {
        "type": "object",
        "description": "Corpo de criar/editar link (e da prévia). Campos desconhecidos → 400.",
        "required": [
          "destino"
        ],
        "properties": {
          "destino": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "http(s) absoluto; sem usuário na URL; não pode ser o próprio encurtador nem endereço interno."
          },
          "titulo": {
            "type": "string",
            "maxLength": 200
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
            "description": "Opcional: <host>/r/<slug>."
          },
          "dominio": {
            "type": "string",
            "description": "Domínio VERIFICADO desta conta (exige slug); vazio = host padrão."
          },
          "ativo": {
            "type": "boolean",
            "default": true
          },
          "status_http": {
            "type": "integer",
            "enum": [
              302,
              307
            ],
            "default": 302
          },
          "utm": {
            "$ref": "#/components/schemas/pages_LinkConfigUTM"
          },
          "parametros": {
            "$ref": "#/components/schemas/pages_LinkParametros"
          },
          "repasse": {
            "$ref": "#/components/schemas/pages_LinkRepasse"
          },
          "origem": {
            "type": "object",
            "description": "Só na criação. `pagina` torna o POST idempotente por (pagina_id, bloco_id, destino).",
            "properties": {
              "tipo": {
                "type": "string",
                "enum": [
                  "manual",
                  "pagina"
                ]
              },
              "pagina_id": {
                "type": "string"
              },
              "bloco_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "pages_Link": {
        "allOf": [
          {
            "$ref": "#/components/schemas/pages_LinkEntrada"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "readOnly": true
              },
              "codigo": {
                "type": "string",
                "readOnly": true,
                "pattern": "^[0-9abcdefghjkmnpqrstvwxyz]{8}$",
                "description": "Código global (Crockford, CSPRNG); nunca reaproveitado."
              },
              "cliques": {
                "type": "integer",
                "format": "int64",
                "readOnly": true,
                "description": "Cliques de gente (robôs de pré-visualização fora)."
              },
              "url_curta": {
                "type": "string",
                "format": "uri",
                "readOnly": true
              },
              "url_slug": {
                "type": "string",
                "format": "uri",
                "readOnly": true
              },
              "criado_em": {
                "type": "string",
                "format": "date-time",
                "readOnly": true
              },
              "atualizado_em": {
                "type": "string",
                "format": "date-time",
                "readOnly": true
              }
            }
          }
        ]
      },
      "pages_ContagemValorLink": {
        "type": "object",
        "properties": {
          "valor": {
            "type": "string",
            "description": "\"\" = direto / sem etiqueta / não identificado"
          },
          "cliques": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "pages_RelatorioLink": {
        "type": "object",
        "description": "Cliques do período, dias em America/Sao_Paulo. Sem IP nem dado pessoal; únicos = visitantes distintos por dia (hash com sal diário).",
        "properties": {
          "de": {
            "type": "string",
            "format": "date"
          },
          "ate": {
            "type": "string",
            "format": "date"
          },
          "total": {
            "type": "integer",
            "format": "int64"
          },
          "unicos": {
            "type": "integer",
            "format": "int64"
          },
          "robos": {
            "type": "integer",
            "format": "int64"
          },
          "por_dia": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "dia": {
                  "type": "string",
                  "format": "date"
                },
                "cliques": {
                  "type": "integer"
                },
                "unicos": {
                  "type": "integer"
                }
              }
            }
          },
          "por_origem": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_ContagemValorLink"
            }
          },
          "por_dispositivo": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_ContagemValorLink"
            }
          },
          "por_pais": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_ContagemValorLink"
            }
          },
          "por_utm_source": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_ContagemValorLink"
            }
          },
          "por_utm_medium": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_ContagemValorLink"
            }
          },
          "por_utm_campaign": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/pages_ContagemValorLink"
            }
          }
        }
      },
      "pages_Marca": {
        "type": "object",
        "description": "Cabeçalho de marca da conta.",
        "properties": {
          "nome_exibicao": {
            "type": "string"
          },
          "logo_url": {
            "type": "string",
            "format": "uri"
          },
          "favicon_url": {
            "type": "string",
            "format": "uri"
          },
          "cor_primaria": {
            "type": "string",
            "description": "cor em hex (ex.: `#b4531f`)"
          }
        }
      },
      "pages_KitMarcaDados": {
        "type": "object",
        "description": "Seções do kit (todas opcionais). Ver `internal/domain/marca`. Imagens só https; cores #rrggbb.",
        "properties": {
          "identidade": {
            "type": "object",
            "properties": {
              "nome_marca": {
                "type": "string",
                "maxLength": 80
              },
              "nome_fantasia": {
                "type": "string",
                "maxLength": 120
              },
              "razao_social": {
                "type": "string",
                "maxLength": 200
              },
              "slogan": {
                "type": "string",
                "maxLength": 120
              },
              "descricao_curta": {
                "type": "string",
                "maxLength": 300
              },
              "descricao_longa": {
                "type": "string",
                "maxLength": 2000
              },
              "logos": {
                "type": "object",
                "properties": {
                  "principal": {
                    "type": "string",
                    "format": "uri"
                  },
                  "horizontal": {
                    "type": "string",
                    "format": "uri"
                  },
                  "icone": {
                    "type": "string",
                    "format": "uri"
                  },
                  "escuro": {
                    "type": "string",
                    "format": "uri"
                  },
                  "usar_logo_da_empresa": {
                    "type": "boolean",
                    "description": "referencia o logo da empresa do admin (core), sem copiar"
                  }
                }
              },
              "favicon_url": {
                "type": "string",
                "format": "uri"
              },
              "cores": {
                "type": "object",
                "properties": {
                  "primaria": {
                    "type": "string"
                  },
                  "secundaria": {
                    "type": "string"
                  },
                  "acento": {
                    "type": "string"
                  },
                  "acento2": {
                    "type": "string"
                  },
                  "fundo": {
                    "type": "string"
                  },
                  "texto": {
                    "type": "string"
                  }
                }
              },
              "fontes": {
                "type": "object",
                "properties": {
                  "titulos": {
                    "type": "string"
                  },
                  "texto": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "compartilhamento": {
            "type": "object",
            "properties": {
              "imagem_url": {
                "type": "string",
                "format": "uri",
                "description": "recomendado 1200×630"
              },
              "imagem_largura": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000
              },
              "imagem_altura": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000
              },
              "imagem_alt": {
                "type": "string",
                "maxLength": 300
              },
              "site_name": {
                "type": "string",
                "maxLength": 80
              },
              "x_handle": {
                "type": "string",
                "description": "normalizado para @usuario"
              },
              "facebook_app_id": {
                "type": "string",
                "description": "só dígitos"
              },
              "facebook_pagina_url": {
                "type": "string",
                "format": "uri"
              },
              "locale": {
                "type": "string",
                "description": "pt_BR"
              }
            }
          },
          "redes": {
            "type": "object",
            "description": "rede → URL canônica https. Chaves: instagram, x, facebook, linkedin, youtube, tiktok, pinterest, threads, whatsapp (→ wa.me), telegram, github, behance, dribbble, spotify, site. Aceita handle ou URL na entrada.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "contato": {
            "type": "object",
            "properties": {
              "telefones": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "object",
                  "properties": {
                    "rotulo": {
                      "type": "string"
                    },
                    "e164": {
                      "type": "string"
                    }
                  }
                }
              },
              "whatsapp": {
                "type": "string",
                "description": "E.164"
              },
              "emails": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "object",
                  "properties": {
                    "rotulo": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    }
                  }
                }
              },
              "horario": {
                "type": "string",
                "maxLength": 300
              }
            }
          },
          "enderecos": {
            "type": "object",
            "properties": {
              "comercial": {
                "$ref": "#/components/schemas/pages_KitMarcaEndereco"
              },
              "fiscal": {
                "$ref": "#/components/schemas/pages_KitMarcaEndereco"
              }
            }
          },
          "legal": {
            "type": "object",
            "properties": {
              "documento": {
                "type": "string",
                "description": "CNPJ (inclusive alfanumérico) ou CPF, validado"
              },
              "tipo_documento": {
                "type": "string",
                "enum": [
                  "cnpj",
                  "cpf",
                  ""
                ],
                "readOnly": true
              },
              "inscricao_estadual": {
                "type": "string"
              },
              "inscricao_municipal": {
                "type": "string"
              },
              "privacidade_url": {
                "type": "string",
                "format": "uri"
              },
              "termos_url": {
                "type": "string",
                "format": "uri"
              },
              "dpo_email": {
                "type": "string",
                "format": "email"
              }
            }
          },
          "voz": {
            "type": "object",
            "description": "contexto da IA; nunca sai para a página pública",
            "properties": {
              "tom_de_voz": {
                "type": "string",
                "maxLength": 300
              },
              "publico": {
                "type": "string",
                "maxLength": 300
              },
              "palavras_chave": {
                "type": "array",
                "maxItems": 30,
                "items": {
                  "type": "string",
                  "maxLength": 40
                }
              }
            }
          }
        }
      },
      "pages_KitMarcaEndereco": {
        "type": "object",
        "properties": {
          "logradouro": {
            "type": "string"
          },
          "numero": {
            "type": "string"
          },
          "complemento": {
            "type": "string"
          },
          "bairro": {
            "type": "string"
          },
          "cidade": {
            "type": "string"
          },
          "uf": {
            "type": "string"
          },
          "cep": {
            "type": "string",
            "description": "BR: 00000-000"
          },
          "pais": {
            "type": "string",
            "description": "ISO-3166 alfa-2"
          },
          "latitude": {
            "type": "string"
          },
          "longitude": {
            "type": "string"
          },
          "maps_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "pages_KitMarca": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "nome": {
            "type": "string",
            "maxLength": 60
          },
          "padrao": {
            "type": "boolean"
          },
          "dados": {
            "$ref": "#/components/schemas/pages_KitMarcaDados"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "pages_KitMarcaResumo": {
        "allOf": [
          {
            "$ref": "#/components/schemas/pages_KitMarca"
          },
          {
            "type": "object",
            "properties": {
              "dominios": {
                "type": "integer"
              },
              "paginas": {
                "type": "integer"
              }
            }
          }
        ]
      },
      "pages_KitMarcaPedido": {
        "type": "object",
        "required": [
          "nome"
        ],
        "properties": {
          "nome": {
            "type": "string",
            "maxLength": 60
          },
          "dados": {
            "$ref": "#/components/schemas/pages_KitMarcaDados"
          },
          "duplicar_de": {
            "type": "string",
            "description": "só na criação"
          }
        }
      },
      "pages_KitMarcaVinculoPedido": {
        "type": "object",
        "properties": {
          "kit_id": {
            "type": "string",
            "description": "vazio = volta ao fallback"
          }
        }
      },
      "pages_KitMarcaVinculos": {
        "type": "object",
        "properties": {
          "dominios": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "dominio_id": {
                  "type": "string"
                },
                "host": {
                  "type": "string"
                },
                "kit_id": {
                  "type": "string"
                },
                "kit_nome": {
                  "type": "string"
                }
              }
            }
          },
          "paginas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "pagina_id": {
                  "type": "string"
                },
                "titulo": {
                  "type": "string"
                },
                "dominio": {
                  "type": "string"
                },
                "slug": {
                  "type": "string"
                },
                "kit_id": {
                  "type": "string"
                },
                "kit_nome": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "pages_KitMarcaEfetivo": {
        "type": "object",
        "properties": {
          "origem": {
            "type": "string",
            "enum": [
              "pagina",
              "dominio",
              "padrao",
              "vazio"
            ]
          },
          "kit": {
            "allOf": [
              {
                "$ref": "#/components/schemas/pages_KitMarca"
              }
            ],
            "nullable": true
          },
          "publico": {
            "type": "object",
            "nullable": true,
            "description": "projeção pública (sem voz) + same_as — a mesma do campo `kit` da página publicada"
          },
          "contexto_ia": {
            "type": "object",
            "description": "o kit como DADO para a geração por IA"
          }
        }
      },
      "pages_RascunhoPagina": {
        "type": "object",
        "properties": {
          "template": {
            "type": "object",
            "description": "o documento como foi gravado (v1 ou v2), sem validação de publicação"
          },
          "rascunho_em": {
            "type": "string",
            "format": "date-time",
            "description": "etiqueta de concorrência; devolva em `?base=`"
          },
          "rascunho_por": {
            "type": "string",
            "description": "`apikey:<key_id>` quando foi uma chave; ausente para pessoa"
          }
        }
      },
      "pages_ConflitoDeRascunho": {
        "type": "object",
        "properties": {
          "erro": {
            "type": "string"
          },
          "codigo": {
            "type": "string",
            "enum": [
              "RascunhoConflito"
            ]
          },
          "rascunho": {
            "$ref": "#/components/schemas/pages_RascunhoPagina"
          }
        }
      },
      "pages_SEOPagina": {
        "type": "object",
        "description": "SEO e compartilhamento de UMA página. Vazio = padrão do runtime.",
        "properties": {
          "titulo": {
            "type": "string",
            "maxLength": 120,
            "description": "<title>; o Google corta em ~60"
          },
          "descricao": {
            "type": "string",
            "maxLength": 320,
            "description": "meta description; o Google corta em ~155"
          },
          "canonical_url": {
            "type": "string",
            "format": "uri",
            "description": "URL absoluta http/https; vazio = a URL da própria página"
          },
          "noindex": {
            "type": "boolean",
            "description": "meta robots noindex + fora do sitemap"
          },
          "og_titulo": {
            "type": "string",
            "maxLength": 120
          },
          "og_descricao": {
            "type": "string",
            "maxLength": 320
          },
          "og_imagem_url": {
            "type": "string",
            "format": "uri",
            "description": "só https"
          },
          "og_imagem_alt": {
            "type": "string",
            "maxLength": 300
          },
          "og_imagem_largura": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000,
            "description": "0 = desconhecida; vai junto com a altura"
          },
          "og_imagem_altura": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        }
      },
      "pages_SEOSite": {
        "type": "object",
        "description": "SEO do host de uma conta (domínio verificado, ou o host da plataforma). Só `noindex` é gravável; o resto espelha o kit de marca efetivo do host (041).",
        "properties": {
          "nome_site": {
            "type": "string",
            "maxLength": 80,
            "description": "og:site_name, sufixo do título padrão, WebSite do JSON-LD"
          },
          "og_imagem_url": {
            "type": "string",
            "format": "uri",
            "description": "imagem padrão de compartilhamento; só https"
          },
          "og_imagem_alt": {
            "type": "string",
            "maxLength": 300
          },
          "og_imagem_largura": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000
          },
          "og_imagem_altura": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000
          },
          "favicon_url": {
            "type": "string",
            "format": "uri",
            "description": "só https; vazio cai no favicon da marca e depois no padrão"
          },
          "org_nome": {
            "type": "string",
            "maxLength": 120,
            "description": "Organization do JSON-LD"
          },
          "org_logo_url": {
            "type": "string",
            "format": "uri",
            "description": "só https"
          },
          "org_url": {
            "type": "string",
            "format": "uri",
            "description": "http/https"
          },
          "noindex": {
            "type": "boolean",
            "description": "robots.txt Disallow: / e noindex em toda página do host"
          },
          "kit": {
            "type": "object",
            "readOnly": true,
            "description": "kit de marca efetivo do host, de onde vêm os demais campos",
            "properties": {
              "id": {
                "type": "string"
              },
              "nome": {
                "type": "string"
              },
              "origem": {
                "type": "string",
                "enum": [
                  "pagina",
                  "dominio",
                  "padrao",
                  "vazio"
                ]
              }
            }
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        }
      },
      "pages_NumerosDoRelatorio": {
        "type": "object",
        "description": "Visitas = sessões distintas; visualizações = eventos page_view; conversões = sessões com form_submit. As `form_*` contam o funil do formulário (taxa de envio = form_envios ÷ form_visualizacoes, conta de quem lê).",
        "properties": {
          "visitas": {
            "type": "integer"
          },
          "visualizacoes": {
            "type": "integer"
          },
          "conversoes": {
            "type": "integer"
          },
          "form_visualizacoes": {
            "type": "integer",
            "description": "eventos form_view (o formulário ficou visível); `extras.form` = id público do formulário"
          },
          "form_inicios": {
            "type": "integer",
            "description": "eventos form_start"
          },
          "form_envios": {
            "type": "integer",
            "description": "eventos form_submit"
          }
        }
      },
      "pages_RelatorioAnalytics": {
        "type": "object",
        "description": "Totais, série diária (dia civil de America/Sao_Paulo) e linhas da dimensão — da MESMA varredura, então a série soma o total.",
        "properties": {
          "de": {
            "type": "string",
            "format": "date-time"
          },
          "ate": {
            "type": "string",
            "format": "date-time"
          },
          "dimensao": {
            "type": "string"
          },
          "totais": {
            "$ref": "#/components/schemas/pages_NumerosDoRelatorio"
          },
          "serie": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/pages_NumerosDoRelatorio"
                },
                {
                  "type": "object",
                  "properties": {
                    "dia": {
                      "type": "string",
                      "format": "date"
                    }
                  }
                }
              ]
            }
          },
          "linhas": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/pages_NumerosDoRelatorio"
                },
                {
                  "type": "object",
                  "properties": {
                    "valor": {
                      "type": "string",
                      "description": "valor da dimensão; `(vazio)` = não preenchido"
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "pages_RecorteDeVisao": {
        "type": "object",
        "required": [
          "periodo",
          "dimensao",
          "metrica"
        ],
        "properties": {
          "periodo": {
            "type": "string",
            "enum": [
              "7d",
              "30d",
              "90d",
              "personalizado"
            ]
          },
          "de": {
            "type": "string",
            "format": "date",
            "description": "só no personalizado (AAAA-MM-DD)"
          },
          "ate": {
            "type": "string",
            "format": "date",
            "description": "só no personalizado; até 92 dias"
          },
          "comparar": {
            "type": "boolean",
            "description": "comparar com o período anterior de mesma duração"
          },
          "dimensao": {
            "type": "string",
            "enum": [
              "pagina",
              "utm_source",
              "utm_medium",
              "utm_campaign",
              "pais",
              "dispositivo",
              "formulario"
            ]
          },
          "metrica": {
            "type": "string",
            "enum": [
              "visitas",
              "visualizacoes",
              "conversoes",
              "taxa"
            ]
          },
          "filtros": {
            "type": "object",
            "description": "chave = dimensão; valor até 200 caracteres; `(vazio)` filtra os não preenchidos",
            "additionalProperties": {
              "type": "string",
              "maxLength": 200
            }
          }
        }
      },
      "pages_VisaoDeRelatorio": {
        "type": "object",
        "description": "Um recorte nomeado da Analytics, compartilhado pela conta.",
        "properties": {
          "id": {
            "type": "string"
          },
          "nome": {
            "type": "string",
            "maxLength": 80
          },
          "recorte": {
            "$ref": "#/components/schemas/pages_RecorteDeVisao"
          },
          "criado_em": {
            "type": "string",
            "format": "date-time"
          },
          "atualizado_em": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "pages_PedidoDeVisao": {
        "type": "object",
        "required": [
          "nome",
          "recorte"
        ],
        "additionalProperties": false,
        "properties": {
          "nome": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "recorte": {
            "$ref": "#/components/schemas/pages_RecorteDeVisao"
          }
        }
      },
      "pages_Variante": {
        "type": "object",
        "description": "Uma variante de um teste A/B da página.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,64}$",
            "description": "id textual da variante (único no experimento; vai em cookie e na credencial do pixel)"
          },
          "nome": {
            "type": "string"
          },
          "peso": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "distribuição de tráfego (0–100)"
          }
        }
      },
      "pages_Roteamento": {
        "type": "object",
        "description": "Regra de desvio de uma página.",
        "required": [
          "estrategia",
          "destinos"
        ],
        "properties": {
          "estrategia": {
            "type": "string",
            "description": "dimensão da regra (ex.: geo, device, idioma)"
          },
          "destinos": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "required": [
                "id"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "peso": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          }
        }
      },
      "pages_LeadAPI": {
        "type": "object",
        "description": "Corpo do modo API da captura (`POST /v1/leads` e `.../submissions`). Campos EXPLÍCITOS — sem bind do corpo inteiro (anti mass assignment). Corpo máximo: 64 KB.",
        "required": [
          "campos"
        ],
        "properties": {
          "form_id": {
            "type": "string",
            "description": "id do formulário; no path tem precedência sobre este campo"
          },
          "campos": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "os valores, por nome de campo declarado",
            "example": {
              "email": "fulana@exemplo.test",
              "nome": "Fulana"
            }
          },
          "id_externo": {
            "type": "string",
            "maxLength": 255,
            "description": "Id desta submissão no SEU sistema. É a chave de deduplicação (escopada por formulário): reenviar o mesmo `id_externo` devolve 200 com `duplicada: true` e o MESMO `lead_id`. O header `Idempotency-Key`, se enviado, vale no lugar.",
            "example": "blog-sub-48213"
          },
          "consentimento": {
            "type": "boolean",
            "description": "true equivale a `campos.consentimento=on` quando o campo não veio. Formulário com `exige_consentimento` recusa (400) sem ele."
          },
          "ocorrido_em": {
            "type": "string",
            "format": "date-time",
            "description": "quando o visitante enviou no seu sistema (RFC 3339). Vira o instante de recebimento do lead. Recusado (400) se estiver mais de 5 min no futuro ou mais de 365 dias no passado.",
            "example": "2026-10-08T14:03:00Z"
          },
          "origem": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "maxLength": 2048,
                "description": "URL da página onde o formulário foi enviado (http/https)",
                "example": "https://blog.exemplo.test/post/lancamento"
              },
              "utm_source": {
                "type": "string"
              },
              "utm_medium": {
                "type": "string"
              },
              "utm_campaign": {
                "type": "string"
              },
              "utm_term": {
                "type": "string"
              },
              "utm_content": {
                "type": "string"
              },
              "referrer": {
                "type": "string"
              }
            }
          }
        }
      },
      "pages_LeadAceito": {
        "type": "object",
        "description": "Resposta da captura. O status separa criado (201) de deduplicado (200) SÓ no modo API.",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "lead_id": {
            "type": "string",
            "format": "uuid"
          },
          "duplicada": {
            "type": "boolean",
            "description": "true quando a submissão foi deduplicada (nada de novo gravado); `lead_id` é o do envio original"
          },
          "id_externo": {
            "type": "string",
            "description": "eco do `id_externo` enviado, quando houve"
          }
        }
      },
      "pages_Pixel": {
        "type": "object",
        "description": "Um ponto de coleta.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "nome": {
            "type": "string"
          },
          "ativo": {
            "type": "boolean"
          },
          "snippet": {
            "type": "string",
            "readOnly": true,
            "description": "a tag pronta com a credencial assinada, para colar no site"
          }
        }
      },
      "pages_PixelUpsert": {
        "type": "object",
        "description": "Corpo de criar/editar pixel. No PATCH, informe `nome` e/ou `ativo`.",
        "properties": {
          "nome": {
            "type": "string",
            "maxLength": 120
          },
          "ativo": {
            "type": "boolean"
          }
        }
      },
      "pages_ItemLixeira": {
        "type": "object",
        "description": "Um item removido que a conta pode restaurar (ADR-0121). Os tipos `link`, `dominio`, `gratis` (subdomínio grátis), `midia`, `kit`, `visao`, `roteamento` e `rascunho` vêm do asender_pages_api; os tipos `pixeldominio` e `pixelconversao` vêm do asender_pages_runtime. Credencial (token Cloudflare) e histórico de destinos de formulário NUNCA entram: só o master.",
        "required": [
          "tipo",
          "id",
          "nome",
          "apagado_em",
          "apagado_por"
        ],
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "link",
              "dominio",
              "gratis",
              "midia",
              "kit",
              "visao",
              "roteamento",
              "rascunho",
              "pixeldominio",
              "pixelconversao"
            ],
            "description": "Tipo do item."
          },
          "id": {
            "type": "string",
            "description": "Id para o restaurar (público do recurso; `pxd_…`/`pxc_…` nos tipos de pixel)."
          },
          "nome": {
            "type": "string",
            "description": "Nome legível: título e endereço do link, host do domínio, nome da mídia/kit/visão, página do roteamento, `Rascunho da página X`, host ou nome da conversão do pixel."
          },
          "apagado_em": {
            "type": "string",
            "format": "date-time"
          },
          "apagado_por": {
            "type": "string",
            "description": "Id do ator que removeu (pode vir vazio)."
          },
          "apagado_por_nome": {
            "type": "string",
            "description": "Nome de quem removeu, quando o auth o resolve (nome, senão e-mail); omitido se não resolvido."
          }
        }
      },
      "pages_DominioDePixel": {
        "type": "object",
        "required": [
          "host"
        ],
        "properties": {
          "host": {
            "type": "string",
            "description": "origem autorizada a coletar com este pixel (allowlist do pixel)"
          }
        }
      },
      "pages_Conversao": {
        "type": "object",
        "description": "Uma conversão rastreada pelo pixel.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "nome": {
            "type": "string",
            "maxLength": 120
          },
          "tipo": {
            "type": "string",
            "description": "como a conversão é detectada (ex.: clique, envio)"
          },
          "seletor": {
            "type": "string",
            "description": "seletor CSS do elemento observado, quando aplicável"
          }
        }
      },
      "pages_LoteDeColeta": {
        "type": "object",
        "description": "Corpo de `POST /collect` — um lote de eventos de comportamento.",
        "required": [
          "credencial",
          "eventos"
        ],
        "properties": {
          "credencial": {
            "type": "string",
            "description": "o token assinado do pixel, embutido na página servida"
          },
          "eventos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "tipo": {
                  "type": "string",
                  "description": "tipo do evento (ex.: pageview, click, scroll)"
                },
                "sessao_id": {
                  "type": "string"
                },
                "ocorreu_em": {
                  "type": "string",
                  "format": "date-time"
                },
                "variante_id": {
                  "type": "string",
                  "description": "a variante de A/B ativa, quando houver"
                },
                "extras": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "pages_LoteDeReplay": {
        "type": "object",
        "description": "Corpo de `POST /replay` — um lote de uma jornada gravada.",
        "required": [
          "credencial",
          "sessao_id",
          "eventos"
        ],
        "properties": {
          "credencial": {
            "type": "string",
            "description": "o token assinado do pixel"
          },
          "sessao_id": {
            "type": "string"
          },
          "pagina": {
            "type": "string"
          },
          "viewport_w": {
            "type": "integer"
          },
          "viewport_h": {
            "type": "integer"
          },
          "eventos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "t": {
                  "type": "integer",
                  "description": "offset em ms desde o início da sessão (o relógio é do SERVIDOR)"
                },
                "tipo": {
                  "type": "string"
                },
                "payload": {
                  "type": "object",
                  "description": "carga do evento (JSON livre)"
                }
              }
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ApiKeyBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer sk_live_...`. Prefixos aceitos: `sk_live_`,\n`sk_test_`, `sk_master_`, mínimo 16 caracteres. Validada no\n`asender_plataforma_api`; tenant não-`active` → 403 `TenantSuspended`.\nChave PÚBLICA (`pk_…`) é recusada com 401 antes do core (M15): ela só\nvale em `POST /v1/push/site/devices`.\n\n**Escopos (autorização por rota, deny-default — ADR-0103).** Cada rota\nexige UM escopo; chave sem ele recebe **403 `InsufficientScope`**\n(`API key lacks the required scope: sms:send.`). Chave com\n`email:send,email:read` NÃO envia SMS, push nem WhatsApp. Rota sem escopo\ndeclarado também é 403.\n\n| escopo | rotas |\n|---|---|\n| `email:send` | `POST /v1/emails/send`, `POST /v1/emails/batch` |\n| `email:read` | `GET /v1/emails`, `GET /v1/emails/{id}` |\n| `sms:send` | `POST /v1/sms/send` |\n| `push:send` | `POST /v1/push/send` |\n| `push:devices` | `GET`/`POST /v1/push/devices` |\n| `whatsapp:send` | `POST /v1/whatsapp/send` |\n| `telegram:send` | `POST /v1/telegram/send` |\n| `whatsweb:send` | `POST /v1/whatsweb/send` |\n| `telegramconta:send` | `POST /v1/telegramconta/send` |\n| `account:read` | `GET /v1/account/usage` |\n| `logs:read` | `GET /v1/logs`, `GET /v1/logs/{id}` (log das requisições da própria conta) |\n| `mail:admin` | administração de domínios e caixas do e-mail, na API do mail (`GET /orgs`, `POST`/`GET /orgs/{orgID}/domains`, `POST`/`GET /domains/{id}/mailboxes`…); não lê mensagens nem envia |\n| `forms:read` | `GET /v1/forms`, `GET /v1/forms/{id}/submissions` (também escopo de app OAuth; na API de Páginas lê os formulários em `api.pages.asender.net`) |\n| `forms:write` | criar/editar/apagar formulários e enviar leads, em `api.pages.asender.net` (não são rotas deste host) |\n| `pages:read` | ler páginas, versões, rascunho, SEO, domínios e mídia da conta, em `api.pages.asender.net` (não são rotas deste host) |\n| `pages:write` | criar e editar páginas (versões, rascunho, SEO, importar, duplicar, mudar de domínio, mídia), em `api.pages.asender.net` (não são rotas deste host) |\n| `pages:publish` | publicar e despublicar páginas (visível ao público), em `api.pages.asender.net` (não são rotas deste host) |\n| `leads:read` | `GET /v1/leads`, `GET /v1/leads/{id}` (também escopo de app OAuth) |\n| `leads:write` | `POST /v1/leads`, `PATCH /v1/leads/{id}` (criar e atualizar leads; **só chave**, nunca app OAuth; não inclui leitura) |\n| `conversions:read` | `GET /v1/conversions` |\n| `analytics:read` | `GET /v1/analytics/{overview,timeseries,breakdown,forms}` |\n| `campaigns:read` | `GET /v1/campaigns*`, `GET /v1/reports/email/{overview,timeseries,top-emails,domains}` |\n| `messaging:read` | `GET /v1/messaging/{sms,whatsapp,push}/messages` e `/reports/overview` |\n| `conversations:read` | `GET /v1/conversations`, `GET /v1/conversations/{id}/messages` |\n| `journeys:read` | `GET /v1/journeys`, `GET /v1/journeys/{id}` |\n| `webhooks:manage` | `GET`/`POST /v1/webhooks`, `GET`/`PATCH`/`DELETE /v1/webhooks/{id}`, `POST /v1/webhooks/{id}/test` (endpoints de webhook da conta; **só chave**, nunca app OAuth) |\n| `dns:read` | `GET /v1/dns/zones`, `GET /v1/dns/zones/{zona}/records`, `GET /v1/dns/changes` (DNS da conta; **só chave**, nunca app OAuth) |\n| `dns:write` | `POST /v1/dns/zones/{zona}/records`, `PATCH`/`DELETE /v1/dns/zones/{zona}/records/{rid}` (**só chave**; não inclui leitura) |\n| (qualquer chave válida) | `GET /v1/account` |\n\n**Escopos são obrigatórios.** Chave **sem lista de escopos**, `full_access`,\n`send_only` ou com alias plural (`emails:*`) NÃO concede nada: recebe\n**403 `InsufficientScope`** em toda rota (exceto `GET /v1/account`).\nMudança 2026-10-07: chaves sem escopo deixam de funcionar — gere outra\ncom escopos do catálogo.\n\n**Só `Authorization: Bearer`.** O header `X-Api-Key` e o parâmetro de\nquery `?api_key=` foram removidos (2026-10-07): sozinhos, dão 401. A\ncredencial em query vazava para access log, Referer e histórico do browser."
      },
      "OAuthBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "`Authorization: Bearer <access token>` de um APP DE TERCEIRO (ADR-0128, \"Entrar com\nAsender\"): JWT RS256 com `aud` = a API pública, `ext=true`, `tok_use=access`,\n`tenant` (a conta escolhida no consentimento) e `scope`. O escopo do token\n**restringe**; o acesso do USUÁRIO que autorizou o app àquela conta e ao produto da\nrota **decide** (PDP, ≤ 60 s para refletir remoção): sem acesso → 403 `Forbidden`.\nSó os escopos de leitura valem (`account:read`, `leads:read`, `conversions:read`,\n`forms:read`, `analytics:read`, `campaigns:read`, `email:read`, `messaging:read`,\n`conversations:read`, `journeys:read`, `logs:read`) e `openid` em `GET /v1/me`; rotas\nde envio/gestão são só de chave. Token de primeira parte (sem `ext`) → 401. Limite de\nrequisições por app × conta."
      },
      "SiteKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "site_key",
        "description": "Chave PÚBLICA de site do Web Push (`pk_push_…`), emitida por\n`GET /api/push/site-config`. Não é segredo (vai no HTML do site): vale\nsó para registrar inscrição, só a partir das origens assinadas nela.\nNa query para o preflight do navegador já carregá-la."
      },
      "SessionBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Token de sessão do usuário final, emitido por `POST /v1/auth/login`."
      },
      "SessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "asender_session",
        "description": "Cookie HttpOnly posto pelo dashboard. Equivalente ao Bearer de sessão."
      },
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_live_… | sk_test_…",
        "description": "API key da conta, criada na Plataforma (`POST https://api.asender.net/api/tenants/{id}/api-keys`, com `scopes`). Vale na autoria de formulários (`forms:read` para ler, `forms:write` para criar/editar/apagar), no envio de lead server-to-server (`forms:write`) e na autoria de PÁGINAS por agentes externos: `pages:read` lê (páginas, rascunho, SEO, domínios, galeria); `pages:write` cria e edita (página, versão, rascunho, SEO, importar, duplicar, mudar de domínio, enviar mídia) e NUNCA publica; `pages:publish` publica e despublica (escopo à parte: é o ato visível ao público). Os escopos NÃO se implicam — um agente que cria e publica precisa dos três. Rotas de página que não aceitam chave (editar slug/título, A/B, roteamento, apagar mídia, SEO do site) respondem 401. Versões e rascunhos feitos por chave levam a autoria `apikey:<key_id>`. Rota fora da tabela de escopos, ou chave sem o escopo, responde 403 (deny-default). A conta é resolvida DA CHAVE (validada no core a cada chamada — chave revogada para de valer na hora); um `tenant_id` no corpo é ignorado. Fora de produção use `sk_test_…`."
      },
      "accessToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT (access token RS256)",
        "description": "O **access token** emitido por `POST /token`, em `Authorization: Bearer <access_token>`. É um JWT RS256 com `tok_use: \"access\"` cujo `aud` é o `client_id`. O ID token NÃO é aceito aqui — ele se valida no client, não se apresenta como credencial."
      },
      "sessao": {
        "type": "http",
        "scheme": "bearer",
        "description": "Sessão OIDC do usuário (OAuth 2.1 + PKCE, emitida por https://auth.asender.net). O token vai em `Authorization: Bearer` ou no cookie `asender_session` (o caminho dos frontends da casa). A conta ativa é indicada em `X-Asender-Tenant` — uma PREFERÊNCIA entre as contas do usuário, nunca uma autorização: o acesso é decidido server-side pelo PEP do produto `pages`."
      }
    },
    "parameters": {
      "platform_DnsZonaPath": {
        "name": "zona",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{1,64}$",
          "examples": [
            "023e105f4ecef8ad9ca31a8372d0c353"
          ]
        },
        "description": "Id da zona (o `id` de `GET /v1/dns/zones`)."
      },
      "platform_DnsRegistroPath": {
        "name": "rid",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{1,64}$"
        },
        "description": "Id do registro (o `id` de `GET /v1/dns/zones/{zona}/records`)."
      },
      "platform_DnsIdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9._:-]{1,128}$"
        },
        "description": "Chave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é\n`400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem\ntocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`."
      },
      "platform_DnsDryRun": {
        "name": "dry_run",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "1",
            "true",
            "0",
            "false"
          ]
        },
        "description": "`1`/`true`: devolve o plano sem tocar a Cloudflare. Outro valor é `400`."
      },
      "platform_DnsConfirmar": {
        "name": "X-Asender-Confirmar",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "description": "Nome completo do registro. **Obrigatório** no `DELETE` e em qualquer escrita em `MX`, SPF ou\nDMARC do apex (`428 ConfirmationRequired` sem ele). Um valor diferente do nome também é recusado."
      },
      "platform_LeadIdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9._:-]{1,128}$"
        },
        "description": "Chave de idempotência da escrita de lead (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada\né `422`). Vale 24 h por conta e rota: a repetição com o mesmo corpo devolve o resultado\noriginal (`Idempotent-Replayed: true`); com outro corpo, `422 IdempotencyKeyReused`."
      },
      "platform_IdempotencyKeyHeader": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9._:-]{1,128}$"
        },
        "description": "Chave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`;\numa chave malformada é recusada com `422`, nunca ignorada). **Tem\nprecedência** sobre o campo `IdempotencyKey` do corpo.\n\n- **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da\n mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem\n cobrado de novo.\n- **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o\n texto novo NÃO é enviado e a mensagem original não muda. Use uma chave\n nova para uma mensagem nova.\n\nO \"mesmo corpo\" ignora a ordem dos campos e o próprio campo\n`IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item\n(`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e\nWhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o\nreplay devolve o `202` original sem comparar o corpo.\n\n**Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria\nestiver indisponível (timeout, conexão recusada ou 5xx do gateway), o\nenvio NÃO falha: a API persiste o pedido em disco e responde **`202` com\n`Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave\nEFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o\ncliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira\ntentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma\nchave** quando ele volta; por isso o replay nunca duplica o envio, nem\nquando a primeira tentativa chegou ao destino e só a resposta se perdeu.\nPara obter os ids depois, reenvie o MESMO `POST` com a mesma\n`Idempotency-Key`: a resposta traz os ids originais e\n`ReusedIdempotency: true`. Erros de validação (`4xx`) continuam\nsíncronos e nunca entram na fila; um pedido aceito que o destino recusar\nde vez no replay (4xx) fica retido em arquivo de mortos para ação\nmanual, com alerta."
      },
      "platform_TenantHeader": {
        "name": "X-Asender-Tenant",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "examples": [
            "acc_demo_sp"
          ]
        },
        "description": "Tenant que o dashboard está olhando. **Nunca é usado como veio:** só\nvale depois de provada a pertinência do usuário (vínculo direto ou a um\nancestral). Fora do escopo → **404**, não 403. Ausente → o menor id\nentre os tenants do usuário, em ordem lexicográfica (escolha estável, e\na razão de o login novo cair numa conta vazia)."
      },
      "platform_TenantPathId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "examples": [
            "acc_demo_sp"
          ]
        },
        "description": "Public id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira."
      },
      "platform_MessageId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "examples": [
            "msg_83fcaff4b496c6e6"
          ]
        },
        "description": "Public id da mensagem (`msg_<hex>`)."
      },
      "platform_From": {
        "name": "from",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "ISO-8601 (`YYYY-MM-DD` ou RFC 3339). Default: 30 dias antes de `to`."
      },
      "platform_To": {
        "name": "to",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "ISO-8601. Default: agora. **Fim EXCLUSIVO**; data pura cobre o dia inteiro."
      },
      "mail_MailboxID": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "id da caixa; o acesso é conferido contra a chave (deny-default)."
      },
      "auth_UserId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "examples": [
            "usr_demo_owner"
          ]
        },
        "description": "Public id (`usr_<hex>`). O BIGSERIAL interno nunca aparece."
      },
      "auth_TenantId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "examples": [
            "acc_1a2b3c"
          ]
        },
        "description": "Public id da conta (`acc_<hex>`) — o mesmo que os BFFs mandam em `X-Asender-Tenant`."
      },
      "auth_LixeiraTipo": {
        "name": "tipo",
        "in": "query",
        "schema": {
          "$ref": "#/components/schemas/auth_LixeiraTipoRecurso"
        },
        "description": "Sem `tipo` lista todos os tipos do servico. Tipo desconhecido = 422."
      },
      "auth_LixeiraTipoPath": {
        "name": "tipo",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/auth_LixeiraTipoRecurso"
        }
      },
      "auth_LixeiraIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_.:@-]{1,128}$"
        }
      },
      "auth_LixeiraCursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string",
          "maxLength": 2048
        },
        "description": "Cursor opaco devolvido em `cursor` (ou `proximo`)."
      },
      "auth_LixeiraLimite": {
        "name": "limite",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "auth_LixeiraIdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "auth_LixeiraIdempotencyKeyOpcional": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Opcional e recomendada; ver secao Idempotencia."
      },
      "auth_LixeiraOperadorId": {
        "name": "X-Asender-Operador-Id",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 128
        },
        "description": "Obrigatorio no modo estrito e em estagio admin/retido."
      },
      "auth_LixeiraOperadorAal": {
        "name": "X-Asender-Operador-Aal",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "1",
            "2"
          ]
        }
      },
      "auth_LixeiraOperadorStepupEm": {
        "name": "X-Asender-Operador-Stepup-Em",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "crm_TenantHeader": {
        "name": "X-Asender-Tenant",
        "in": "header",
        "required": false,
        "description": "Conta em que a requisição opera — id OU slug. É uma preferência conferida contra as contas da sessão (tenant fora da lista → 404). Ausente, cai na primeira conta da sessão.",
        "schema": {
          "type": "string",
          "examples": [
            "acme-corp"
          ]
        }
      },
      "crm_Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "id público do recurso.",
        "schema": {
          "type": "string"
        }
      },
      "crm_Limite": {
        "name": "limite",
        "in": "query",
        "required": false,
        "description": "máximo de itens por página (inteiro positivo).",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "crm_Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "cursor opaco devolvido em `proximo`; adulterado → 400.",
        "schema": {
          "type": "string"
        }
      },
      "pages_ExternalID": {
        "name": "external_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9._:-]+$"
        },
        "description": "id do formulário no sistema do integrador (campo `external_id`)."
      },
      "pages_IdempotencyKeyForm": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        },
        "description": "Chave de idempotência da criação (1–255 caracteres, única por conta). Repetir a mesma chave com o MESMO corpo devolve 201 com o MESMO formulário e o header `Idempotent-Replayed: true`; com corpo diferente, 422."
      },
      "pages_IdempotencyKeyLead": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        },
        "description": "Alternativa ao `id_externo` do corpo como chave de deduplicação (por formulário)."
      },
      "pages_PaginaID": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "id da página; o acesso é conferido contra a sessão (deny-default)."
      },
      "pages_PixelID": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "pages_De": {
        "name": "de",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "description": "início da janela (RFC3339). Ausente usa os últimos 7 dias; teto de 92 dias."
      },
      "pages_Ate": {
        "name": "ate",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "description": "fim da janela (RFC3339)."
      },
      "pages_PaginaFiltro": {
        "name": "pagina",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "recorta o relatório por página; ausente agrega todas."
      }
    },
    "responses": {
      "platform_DataBadRequest": {
        "description": "`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_DataError"
            }
          }
        }
      },
      "platform_DataNotFound": {
        "description": "`404 NotFound` — o recurso não existe **ou é de outra conta** (mesma resposta).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_DataError"
            }
          }
        }
      },
      "platform_DataForbidden": {
        "description": "`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`)\nou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto\n(`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.",
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/platform_DataError"
                },
                {
                  "$ref": "#/components/schemas/platform_ErrorEnvelope"
                }
              ]
            }
          }
        }
      },
      "platform_DataBadGateway": {
        "description": "`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_DataError"
            }
          }
        }
      },
      "platform_DataUnavailable": {
        "description": "`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_DataError"
            }
          }
        }
      },
      "platform_Enqueued": {
        "description": "Aceito e persistido; a entrega é assíncrona. Com o serviço dono indisponível, `Status: accepted` (spool durável, replay com a mesma `Idempotency-Key`).",
        "headers": {
          "Idempotency-Key": {
            "schema": {
              "type": "string"
            },
            "description": "Só com `Status: accepted`: a chave efetiva do replay."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                },
                {
                  "type": "object",
                  "properties": {
                    "MessageId": {
                      "type": "string",
                      "description": "Primeiro id — mantém a forma SES de um destinatário."
                    },
                    "MessageIds": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Um id POR destinatário: N destinatários criam N mensagens."
                    },
                    "Status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "accepted"
                      ],
                      "description": "`queued`: persistido no serviço dono. `accepted`: o serviço dono estava indisponível; o pedido está no spool durável da API e será reenviado com a mesma `Idempotency-Key` (`MessageIds` vem vazio; veja `IdempotencyKey`)."
                    },
                    "IdempotencyKey": {
                      "type": "string",
                      "description": "Só com `Status: accepted`: a chave efetiva do replay (também no header `Idempotency-Key`). Reenvie o POST com ela para obter os ids."
                    },
                    "ReusedIdempotency": {
                      "type": "boolean",
                      "description": "`true` quando a `Idempotency-Key` já existia com o MESMO corpo: ids da mensagem original, nada reenviado."
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "platform_EnqueuedEmail": {
        "description": "Aceito e persistido; a entrega é assíncrona. Destinatários suprimidos vêm em `Suppressed[]`. Com o serviço de mensageria indisponível, `Status: accepted` (spool durável; replay com a mesma `Idempotency-Key`).",
        "headers": {
          "Idempotency-Key": {
            "schema": {
              "type": "string"
            },
            "description": "Só com `Status: accepted`: a chave efetiva do replay."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/platform_RequestIdEnvelope"
                },
                {
                  "type": "object",
                  "properties": {
                    "MessageId": {
                      "type": "string",
                      "description": "Primeiro id — mantém a forma SES de um destinatário."
                    },
                    "MessageIds": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Um id POR destinatário enviável: N destinatários criam N mensagens."
                    },
                    "Status": {
                      "type": "string",
                      "enum": [
                        "queued",
                        "accepted"
                      ],
                      "description": "`accepted`: o serviço de mensageria estava indisponível; pedido no spool durável, reenviado com a mesma `Idempotency-Key` (`MessageIds` vazio)."
                    },
                    "IdempotencyKey": {
                      "type": "string",
                      "description": "Só com `Status: accepted`: a chave efetiva do replay."
                    },
                    "ReusedIdempotency": {
                      "type": "boolean",
                      "description": "`true` quando a `Idempotency-Key` já existia com o MESMO corpo: ids da mensagem original, nada reenviado."
                    },
                    "Suppressed": {
                      "type": "array",
                      "description": "Presente só quando algum destinatário foi suprimido. Esses NÃO foram enfileirados.",
                      "items": {
                        "$ref": "#/components/schemas/platform_SuppressedRecipient"
                      }
                    }
                  }
                }
              ]
            },
            "example": {
              "RequestId": "req_01",
              "MessageId": "msg_1",
              "MessageIds": [
                "msg_1"
              ],
              "Status": "queued",
              "ReusedIdempotency": false,
              "Suppressed": [
                {
                  "Email": "teste@cliente.hml",
                  "Reason": "tld_inexistente",
                  "Message": "Não enviado: o domínio \"cliente.hml\" usa um TLD que não existe. Envio suprimido pelo Asender."
                }
              ]
            }
          }
        }
      },
      "platform_RemetenteOuSuprimido": {
        "description": "`422 SuppressedRecipient` — TODOS os destinatários estão suprimidos; nada\nfoi enfileirado. `Message` traz o(s) texto(s) humano(s) e\n`Details.Suppressed[]` a lista `{Email, Reason, Message}`.\n\n`422 RemetenteNaoVerificado` — o remetente não é de domínio verificado\nnesta conta (ver `RemetenteOuInvalido`). `422 ValidationError` — demais\nrecusas do upstream.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelopeSuppressed"
            },
            "example": {
              "RequestId": "req_01",
              "Error": {
                "Type": "Sender",
                "Code": "SuppressedRecipient",
                "Message": "Não enviado: o domínio \"cliente.hml\" usa um TLD que não existe. Envio suprimido pelo Asender.",
                "Details": {
                  "Suppressed": [
                    {
                      "Email": "teste@cliente.hml",
                      "Reason": "tld_inexistente",
                      "Message": "Não enviado: o domínio \"cliente.hml\" usa um TLD que não existe. Envio suprimido pelo Asender."
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "platform_IdempotencyConflict": {
        "description": "`409 IdempotencyConflict` — a `Idempotency-Key` já foi usada com um corpo\nDIFERENTE. Nada foi enviado. Use uma chave nova, ou reenvie o corpo\noriginal para obter o replay (`ReusedIdempotency: true`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_Validation": {
        "description": "`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field \"X\"`) ou tipo errado.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_ValidationDetails": {
        "description": "`400 ValidationError` com `Error.Details` mapeando campo → motivo.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_Unauthorized": {
        "description": "`401 AuthorizationError` — credencial ausente, malformada, inválida ou\nexpirada.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_TenantSuspended": {
        "description": "`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_NoTenant": {
        "description": "`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_NotFoundOrDenied": {
        "description": "`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe,\nou existe e não é seu. Diferenciar seria oráculo de enumeração.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_Conflict": {
        "description": "`409 Conflict` — email já registrado, slug em uso.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_PayloadTooLarge": {
        "description": "`413 PayloadTooLarge` — corpo acima de 2 MiB.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_Unprocessable": {
        "description": "`422 ValidationError` — semântica inválida: parâmetro de query fora da\nallowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo\nde PATCH sem o campo obrigatório, ou recusa do upstream repassada.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_LogsValidation": {
        "description": "`400 ValidationError` — parâmetro inválido, cursor adulterado ou de outra\nconta, `after` com `before`. Corpo repassado do serviço de logs\n(`LogsErrorEnvelope`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_LogsErrorEnvelope"
            }
          }
        }
      },
      "platform_LogsNotFound": {
        "description": "`404 NotFound` — id inexistente **ou de outra conta** (corpo idêntico, sem\noráculo de existência). Corpo repassado do serviço de logs.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_LogsErrorEnvelope"
            }
          }
        }
      },
      "platform_LogsUnavailable": {
        "description": "`503 ServiceUnavailable` — o serviço de logs não respondeu. Repita depois\ndo `Retry-After`; o resto da API segue funcionando.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Segundos até tentar de novo."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_DnsForbidden": {
        "description": "Sem o escopo (`InsufficientScope`), chave de app OAuth, conta suspensa, escrita fora de produção (`NonProductionBlocked`) ou o token Cloudflare sem permissão na zona (`ProviderPermissionDenied`). `Error.Details.Code` traz o código estável do serviço de DNS.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_DnsNotFound": {
        "description": "Zona, registro ou conexão inexistente **ou de outra conta** (`NotFound`, idêntico nos dois casos).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_DnsRejected": {
        "description": "Recusado pelas regras do DNS: `ProtectedRecord` (NS no apex), `ProviderRejected` (a Cloudflare não aceitou), `IdempotencyKeyReused`. `Error.Details` traz `Code` e `Detail`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_DnsConfirmationRequired": {
        "description": "`ConfirmationRequired`: envie `X-Asender-Confirmar` com o nome do registro (ou use `dry_run=1` para pré-visualizar).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_DnsUnavailable": {
        "description": "Serviço de DNS fora do ar ou a Cloudflare indisponível (`ServiceUnavailable`/`ProviderUnavailable`); tente de novo depois de `Retry-After`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_Throttled": {
        "description": "`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Segundos."
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Reset": {
            "schema": {
              "type": "integer"
            },
            "description": "Epoch unix."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_BadGateway": {
        "description": "`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica\nno log com `RequestId`; o cliente recebe mensagem genérica.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_RemetenteOuInvalido": {
        "description": "`422 RemetenteNaoVerificado` — o remetente (`Source`/`From`) de e-mail\nnão é de domínio verificado nesta conta (inclui domínio verificado em\nOUTRA conta). `Message` para o usuário: o domínio e o link para Admin →\nDomínios. Nada é enfileirado (decisão de 2026-10-02; antes o e-mail\nsaía com o remetente da plataforma).\n\n`422 ValidationError` — demais recusas do upstream (template sem\nvariável, todos suprimidos, portão de qualidade…).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "platform_VerificacaoIndisponivel": {
        "description": "`503 VerificacaoIndisponivel` — a verificação do domínio do remetente\nnão respondeu. Fail-closed: nada foi enfileirado. Repita depois do\n`Retry-After` (com a mesma `Idempotency-Key`).",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Segundos até tentar de novo."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/platform_ErrorEnvelope"
            }
          }
        }
      },
      "mail_NaoAutorizado": {
        "description": "API key ausente ou inválida.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/mail_Erro"
            }
          }
        }
      },
      "mail_NaoEncontrado": {
        "description": "Recurso inexistente ou fora do escopo da chave (não revela existência em outra conta).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/mail_Erro"
            }
          }
        }
      },
      "mail_PedidoInvalido": {
        "description": "Parâmetro de query fora do conjunto aceito.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/mail_Erro"
            }
          }
        }
      },
      "auth_AppRevisado": {
        "description": "O app depois da decisão da equipe (ADR-0130).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "app": {
                  "$ref": "#/components/schemas/auth_AppPortal"
                }
              }
            }
          }
        }
      },
      "auth_BadRequest": {
        "description": "`400` — `InvalidRequest` (JSON inválido, **campo desconhecido**, corpo\nvazio, corpo acima de 1 MiB), `InvalidInput` (validação de domínio) ou\n`TwoFactorMissing` (2FA não configurado).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_ServiceUnauthorized": {
        "description": "`401` — token de serviço ausente ou inválido. **Não ocorre com\n`ENV=development`.**",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_SessionUnauthorized": {
        "description": "`401` — `MissingToken` (sem `Authorization: Bearer`), `InvalidToken`\n(assinatura/formato) ou `ExpiredToken` (expirada **ou revogada por\nlogout**).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_TwoFactorFailed": {
        "description": "`401 TwoFactorFailed` — código TOTP inválido.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_TooManyRequests": {
        "description": "`429 TooManyRequests` — trava por tentativas atingida (N códigos errados\nconsecutivos). A conta destrava sozinha após a janela. Quando o prazo é\nconhecido, vem `Retry-After` (segundos) e `Details.retry_after_s`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_NotFound": {
        "description": "`404 NotFound`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_Conflict": {
        "description": "`409 Conflict` — email já cadastrado.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_Forbidden": {
        "description": "`403 Forbidden` — o solicitante não governa esta conta (PDP nega).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_UnprocessableEntity": {
        "description": "`422 Unprocessable` — produto ou papel inexistente no catálogo.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_InternalError": {
        "description": "`500 InternalError`.\n\n**DIVERGÊNCIA:** o `Message` recebe `err.Error()` CRU, como no\n`asender_plataforma_api`. Pode vazar estrutura interna.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_ReauthRequired": {
        "description": "`403 ReauthRequired` — falta reauth recente (≤10 min) DESTA sessão por um\nmétodo aceito; `Error.Details` (schema `MeReauthRequiredDetails`) traz\n`metodos_aceitos` (os que a conta TEM), `metodos` (P5: cada método da política\ncom `disponivel` e o `motivo` do indisponível — ex.: `email_otp` →\n`EmailNaoVerificado`) e, quando `email_otp` é aceito, `destinos`\n[{email_id, mascarado, primario}] para a tela oferecer a escolha.\n`401 DesafioExpirado` (confirm por passkey): desafio vencido, já usado ou de outra sessão.\n`403 ReauthIndisponivel` — o Bearer não é sessão do auth (sem `sid`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_SessaoNecessaria": {
        "description": "`403 SessaoNecessaria` — leitura de segurança (e-mails, fatores, sessões,\ntrilha) com credencial que não é sessão do auth (token de app OIDC, sem\n`sid`): deny-default.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_EnderecoNaoRecebe": {
        "description": "`422 EnderecoNaoRecebe` — o servidor do destinatário recusou o endereço de\nvez (destinatário inexistente/domínio sem correio, 422 do relay). Sem retry.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_EnvioRecusado": {
        "description": "`502 EnvioRecusado` — o envio foi recusado de vez por política do destino\n(problema do remetente, não do endereço). Sem retry; alerta.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_NotImplemented": {
        "description": "`501 NotImplemented` — rota do contrato ainda não implementada (Onda 0).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/auth_ErrorEnvelope"
            }
          }
        }
      },
      "auth_LixeiraValidacao": {
        "description": "Requisicao invalida (400) - parametro obrigatorio ausente ou mal formado.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/auth_LixeiraProblem"
            }
          }
        }
      },
      "auth_LixeiraNaoAutenticado": {
        "description": "Sem credencial valida (nao_autenticado).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/auth_LixeiraProblem"
            }
          }
        }
      },
      "auth_LixeiraProibido": {
        "description": "Autenticado sem permissao (proibido) ou operador sem 2o fator/step-up (stepup_obrigatorio).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/auth_LixeiraProblem"
            }
          }
        }
      },
      "auth_LixeiraNaoEncontrado": {
        "description": "Item inexistente OU de outro tenant (identicos).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/auth_LixeiraProblem"
            }
          }
        }
      },
      "auth_LixeiraConflito": {
        "description": "Conflito: conflito_unicidade, pai_apagado (traz `pai`), estagio_invalido, retido, nao_retido, filhos_vivos, purga_em_curso, idempotencia_em_andamento.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/auth_LixeiraProblem"
            }
          }
        }
      },
      "auth_LixeiraInvalido": {
        "description": "Semantica invalida (422): tipo_desconhecido, cursor_invalido, validacao, idempotencia_reutilizada.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/auth_LixeiraProblem"
            }
          }
        }
      },
      "auth_LixeiraLimiteExcedido": {
        "description": "Limite de taxa excedido (limite_excedido).",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/auth_LixeiraRetryAfter"
          },
          "RateLimit": {
            "$ref": "#/components/headers/auth_LixeiraRateLimit"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/auth_LixeiraProblem"
            }
          }
        }
      },
      "auth_LixeiraIndisponivel": {
        "description": "Dependencia fora (indisponivel). Nunca lista vazia no lugar de erro.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/auth_LixeiraProblem"
            }
          }
        }
      },
      "crm_SemSessao": {
        "description": "sessão ausente ou inválida.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/crm_Erro"
            },
            "example": {
              "erro": "sessão ausente"
            }
          }
        }
      },
      "crm_NaoEncontrado": {
        "description": "recurso inexistente, de outra conta, ou sem acesso à ferramenta.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/crm_Erro"
            },
            "example": {
              "erro": "contato não encontrado"
            }
          }
        }
      },
      "crm_CorpoInvalido": {
        "description": "corpo malformado, campo desconhecido ou query inválida.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/crm_Erro"
            },
            "example": {
              "erro": "corpo inválido: campo desconhecido"
            }
          }
        }
      },
      "crm_RegraInvalida": {
        "description": "corpo bem formado, mas o conteúdo não passa na regra de negócio.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/crm_Erro"
            },
            "example": {
              "erro": "a jornada precisa de um nome"
            }
          }
        }
      },
      "pages_NaoAutorizadoAutoria": {
        "description": "Sem sessão / sem tenant na sessão.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/pages_ErroAutoria"
            }
          }
        }
      },
      "pages_NaoEncontradoAutoria": {
        "description": "Recurso inexistente ou fora do escopo da sessão (não revela existência em outra conta).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/pages_ErroAutoria"
            }
          }
        }
      },
      "pages_PedidoInvalidoAutoria": {
        "description": "Parâmetro de query fora do conjunto aceito.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/pages_ErroAutoria"
            }
          }
        }
      },
      "pages_NaoAutorizadoRuntime": {
        "description": "Sem sessão / credencial ausente ou inválida.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/pages_ErroRuntime"
            }
          }
        }
      },
      "pages_SemEscopoAutoria": {
        "description": "A chave não tem o escopo exigido pela rota (ou a rota não aceita chave). Ex.: `{\"erro\":\"a chave não tem o escopo forms:write\"}`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/pages_ErroAutoria"
            },
            "example": {
              "erro": "a chave não tem o escopo forms:write"
            }
          }
        }
      },
      "pages_SemEscopoRuntime": {
        "description": "A chave não tem o escopo `forms:write`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/pages_ErroRuntime"
            },
            "example": {
              "error": {
                "message": "a chave não tem o escopo forms:write"
              }
            }
          }
        }
      },
      "pages_ChaveIndisponivelAutoria": {
        "description": "Não foi possível validar a chave agora (core fora do ar). Tente de novo.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/pages_ErroAutoria"
            }
          }
        }
      },
      "pages_LimiteDeChaveAutoria": {
        "description": "Escritas demais por esta chave (teto por minuto: 60 nas escritas de `pages:write`, 20 nas de `pages:publish`). Header `Retry-After: 60`.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            },
            "description": "segundos até tentar de novo"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/pages_ErroAutoria"
            }
          }
        }
      }
    },
    "headers": {
      "auth_LixeiraRetryAfter": {
        "description": "Segundos ate poder repetir.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "auth_LixeiraRateLimit": {
        "description": "RateLimit (IETF), ex.: limit=60, remaining=0, reset=30",
        "schema": {
          "type": "string"
        }
      }
    }
  }
}