{
  "openapi": "3.0.3",
  "info": {
    "title": "API CRM Mailpro™ v3",
    "version": "3",
    "description": "A API CRM do Mailpro™ v3 permite-lhe gerir programaticamente contactos, listas, etiquetas,\ncampos personalizados, modelos de e-mail e campanhas — e enviar tanto e-mails de marketing em massa\ncomo e-mails transacionais individuais, bem como SMS.\n\n## Autenticação\n\nTodos os endpoints (exceto `POST /token` e os endpoints OAuth) aceitam **três\nmétodos de autenticação**. Utilize aquele que melhor se adequar à sua stack.\n\n- **OAuth 2.0** (authorization_code + PKCE, ou client_credentials) — a\n  escolha moderna para aplicações de terceiros e plataformas de integração\n  (Zapier, Make, n8n) que atuam em nome de um utilizador Mailpro. Emite tokens de\n  acesso revogáveis e com âmbito definido. **O único método que transporta âmbitos\n  granulares e que pode entregar com segurança a um terceiro.**\n- **Bearer JWT** (legado) — um token HMAC-HS256 obtido via `POST /token`. Parece\n  OAuth, mas está associado a uma única credencial de conta e não possui âmbitos.\n- **IdClient + ApiKey** (legado) — o seu ID de cliente + chave de API em cabeçalhos HTTP.\n\n> O Bearer JWT legado e o IdClient+ApiKey **não estão sujeitos a controlo de âmbito** — mantêm\n> acesso total a qualquer endpoint que já podiam alcançar. Apenas os tokens de acesso\n> OAuth 2.0 estão sujeitos às verificações de âmbito por endpoint apresentadas em cada\n> operação abaixo (um token sem o âmbito necessário recebe **403 `insufficient_scope`**).\n\n### Método 1 — OAuth 2.0 (recomendado para integrações)\n\nO Mailpro executa **um único** servidor de autorização OAuth 2.0 para todas as suas APIs,\ndisponibilizado numa superfície única, independente da versão: **`https://api.mailpro.com/oauth/`**.\nUm servidor, um token — o **mesmo** token de acesso funciona em CRM v3,\nEmail v2 e SMS. Os endpoints que um token pode chamar são determinados pelos\n**âmbitos** do token, e não pelo URL base da API que invoca. Não existe um\nendpoint OAuth por versão (não há `/v3/oauth`, `/v2/oauth` nem `/sms/oauth`).\n\n| Endpoint | URL |\n|----------|-----|\n| Autorização | `GET https://api.mailpro.com/oauth/authorize` |\n| Token / Refresh | `POST https://api.mailpro.com/oauth/token` |\n| JWKS (verificar assinaturas) | `GET https://api.mailpro.com/oauth/.well-known/jwks` |\n| Descoberta | `GET https://api.mailpro.com/oauth/.well-known/openid-configuration` |\n\nEmissor (a claim `iss` em cada token): `https://api.mailpro.com/oauth/`.\n\n**Concessões suportadas**\n\n- `authorization_code` **+ PKCE** (`code_challenge_method=S256` é\n  **obrigatório**) — o fluxo de consentimento no navegador \"Connect Mailpro\". Devolve um\n  token de acesso **e** um refresh token rotativo.\n- `client_credentials` — máquina a máquina, sem utilizador. Devolve apenas um\n  token de acesso.\n- `refresh_token` — troca um refresh token por um novo par access/refresh.\n  **Os refresh tokens rodam**: cada utilização invalida o anterior.\n\n**Tempos de vida dos tokens**\n\n| Token | TTL |\n|-------|-----|\n| Token de acesso (JWT RS256) | **20 minutos** |\n| Refresh token (deslizante, utilização única) | **30 dias** |\n| Código de autorização | **60 segundos** |\n\n**Authorization Code + PKCE — passo a passo**\n\n1. Gere `code_verifier` (43–128 caracteres aleatórios) e\n   `code_challenge = base64url(sha256(code_verifier))`.\n2. Redirecione o utilizador para:\n   ```\n   GET https://api.mailpro.com/oauth/authorize\n     ?response_type=code\n     &client_id=YOUR_CLIENT_ID\n     &redirect_uri=https://yourapp.example.com/callback\n     &scope=contacts:read contacts:write\n     &state=RANDOM_CSRF_TOKEN\n     &code_challenge=CODE_CHALLENGE\n     &code_challenge_method=S256\n   ```\n3. O utilizador aprova no ecrã de consentimento do Mailpro; o Mailpro redireciona de volta\n   com `?code=…&state=…`. Verifique o `state` (proteção CSRF).\n4. Troque o código por tokens:\n   ```\n   POST https://api.mailpro.com/oauth/token\n   Content-Type: application/x-www-form-urlencoded\n\n   grant_type=authorization_code&code=AUTH_CODE\n   &redirect_uri=https://yourapp.example.com/callback\n   &client_id=YOUR_CLIENT_ID&code_verifier=CODE_VERIFIER\n   ```\n   ```json\n   { \"access_token\": \"eyJ…\", \"token_type\": \"Bearer\",\n     \"expires_in\": 1200, \"refresh_token\": \"…\" }\n   ```\n5. Invoque qualquer API do Mailpro com `Authorization: Bearer ACCESS_TOKEN`.\n6. Quando expirar, renove com `grant_type=refresh_token` e guarde o\n   **novo** refresh token (o antigo deixou de ser válido).\n\n**client_credentials**\n\n```\nPOST https://api.mailpro.com/oauth/token\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=client_credentials&client_id=YOUR_CLIENT_ID\n&client_secret=YOUR_CLIENT_SECRET&scope=contacts:read email:send\n```\n\nO mesmo token de acesso OAuth é igualmente aceite pelas APIs Email v2\n(`api.mailpro.com/v2`) e SMS (`api.mailpro.com/sms`) — autentique-se\numa vez, invoque qualquer API. Catálogo completo de âmbitos: ver **Âmbitos OAuth 2.0** abaixo.\n\n### Teste o OAuth 2.0 em minutos — guia rápido\n\nA forma mais rápida de experimentar o OAuth é a concessão **client_credentials**\n(servidor a servidor, sem navegador, sem utilizador). Necessita de um par `client_id` /\n`client_secret` (peça ao seu contacto no Mailpro, ou crie um em **Definições →\nAcesso à API**). Substitua os marcadores de posição abaixo — **nunca submeta para o repositório um segredo\nreal**.\n\n**1. Obter um token de acesso**\n\n```bash\ncurl -X POST https://api.mailpro.com/oauth/token \\\n  -H \"Content-Type: application/x-www-form-urlencoded\" \\\n  -d \"grant_type=client_credentials\" \\\n  -d \"client_id=YOUR_CLIENT_ID\" \\\n  -d \"client_secret=YOUR_CLIENT_SECRET\" \\\n  -d \"scope=contacts:read\"\n```\n\n```json\n{ \"access_token\": \"eyJhbGciOiJSUzI1Ni…\", \"token_type\": \"Bearer\",\n  \"expires_in\": 1200 }\n```\n\n**2. Invocar um endpoint de recurso com o token**\n\n```bash\ncurl https://api.mailpro.com/v3/List \\\n  -H \"Authorization: Bearer eyJhbGciOiJSUzI1Ni…\"\n```\n\nO **mesmo** token funciona também em Email v2 (`…/v2/…`) e SMS (`…/sms/…`) —\ndesde que transporte o âmbito exigido por cada endpoint. Um token sem o\nâmbito necessário é rejeitado com **403 `insufficient_scope`**.\n\n**\"Connect Mailpro\" interativo (authorization_code + PKCE)** — quando atua\nem nome de um *utilizador* (Zapier/Make/n8n, aplicações parceiras): redirecione o utilizador para\n`GET https://api.mailpro.com/oauth/authorize` com `response_type=code`,\n`client_id`, `redirect_uri`, `scope`, `state`, `code_challenge` e\n`code_challenge_method=S256`; o utilizador dá o seu consentimento; troca o\n`code` devolvido em `POST https://api.mailpro.com/oauth/token` com o\n`code_verifier` correspondente. Explicação completa acima; âmbitos em **Âmbitos OAuth 2.0** abaixo.\n\n> Os tokens são **JWTs RS256** — os tokens de acesso vivem **20 minutos**, os refresh\n> tokens **30 dias** (deslizantes, utilização única: cada renovação roda o token).\n> Verifique as assinaturas contra o JWKS em\n> `https://api.mailpro.com/oauth/.well-known/jwks` e valide o âmbito de\n> cada pedido contra a matriz de âmbitos abaixo.\n\n### Método 2 — Bearer JWT (legado)\n\n```\nAuthorization: Bearer <access_token>\n```\n\nObtenha um token via `POST /token` (ver abaixo). Válido por **30 dias** por predefinição;\nuse `grant_type=refresh_token` para renovar sem voltar a autenticar-se. Mais simples de\nconfigurar, mas está associado a uma única credencial de conta e **não pode** ter âmbitos definidos nem\nser entregue a um terceiro — prefira OAuth 2.0 para qualquer integração instalável.\n\n### Método 3 — IdClient + ApiKey (mais simples)\n\nEnvie o seu ID de cliente e a sua chave de API como **cabeçalhos HTTP** (preferível) ou como\n**parâmetros de query**:\n\n```\nIdClient: 12345\nApiKey:   a1b2c3d4-e5f6-7890-abcd-ef1234567890\n```\n\nAs mesmas credenciais das APIs v2 / SMS — copie-as a partir de\n**Definições → Acesso à API**. Sem passo de troca de token. A forma em cabeçalho é recomendada\nporque os URLs são registados pelo IIS / Cloudflare / proxies.\n\n### Endpoint de token (concessão password)\n```\nPOST /token\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=password&username=YOUR_USERNAME&password=YOUR_API_KEY\n```\n- `username` — o seu nome de utilizador Mailpro (e-mail)\n- `password` — a sua chave de API (GUID), disponível em **Definições → Acesso à API**\n\n### Endpoint de token (concessão client_credentials)\n```\nPOST /token\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=client_credentials&client_id=YOUR_CLIENT_ID\n```\n\n> O token emitido por `POST /v3/token` também é válido na API Email v2\n> (`api.mailpro.com/v2`) e na API SMS (`api.mailpro.com/sms`). Autentique-se\n> uma vez, invoque qualquer API.\n\n## Âmbitos OAuth 2.0\n\nQuando a sua aplicação pede um token de acesso OAuth, declara um conjunto de **âmbitos** —\npermissões que definem exatamente o que o token pode fazer. O utilizador vê uma\ndescrição em linguagem corrente de cada âmbito solicitado no ecrã de consentimento.\nOs âmbitos são separados por espaços no parâmetro `scope`\n(`scope=contacts:read email:send`). Solicite apenas o que necessita.\n\nOs âmbitos seguem `<recurso>:<ação>` e são **independentes hierarquicamente** —\n`contacts:write` **não** implica `contacts:read`; solicite ambos, se necessário.\n\n| Âmbito | Permite |\n|-------|--------|\n| `contacts:read` | Consultar os seus contactos, listas, etiquetas, segmentos e campos personalizados. |\n| `contacts:write` | Criar, editar e apagar os seus contactos, listas, etiquetas e campos personalizados. |\n| `email:send` | Enviar e-mails transacionais e de campanha em seu nome. |\n| `email:read` | Consultar estatísticas de entrega, aberturas, cliques e histórico de bounces. |\n| `email:templates:read` | Consultar os seus modelos de e-mail (assunto, HTML, corpo em texto simples). |\n| `email:templates:write` | Criar, editar e apagar os seus modelos de e-mail. |\n| `sms:send` | Enviar mensagens SMS transacionais e de campanha em seu nome. |\n| `sms:read` | Consultar o saldo de créditos SMS e o histórico de mensagens. |\n| `sms:contacts:read` | Consultar a sua agenda telefónica SMS. |\n| `sms:contacts:write` | Criar, editar e apagar entradas na sua agenda telefónica SMS. |\n| `webhooks:manage` | Subscrever, editar e remover endpoints de webhook para a sua conta. |\n| `account:read` | Consultar os créditos, limites e estado da subscrição da sua conta. |\n\n### Aplicação de âmbitos por endpoint\n\nAs verificações de âmbito são **em tempo real** e aplicadas por endpoint para tokens emitidos via OAuth\n(a autenticação legada está isenta). Cada operação nesta referência indica o seu âmbito\nnecessário em **Authorization**. Resumo para a superfície CRM v3:\n\n| Endpoints | Âmbito necessário |\n|-----------|----------------|\n| `GET /Contact/{id}`, `GET /List`, `GET /List/{id}`, `GET /List/{id}/Contact`, `GET /List/{listId}/Unsubscribers`, `GET /Tag`, `GET /Tag/{id}`, `GET /Tag/{id}/Contact`, `GET /Segment`, verificação de pertença a segmento | `contacts:read` |\n| `POST /Contact`, `POST /Contact/Multiple`, `PUT`/`DELETE /Contact/{id}`, adicionar/remover etiqueta ou lista num contacto, `POST`/`PUT`/`DELETE /List`, `POST`/`PUT`/`DELETE /Tag` | `contacts:write` |\n\n> Os endpoints desta especificação que **não** estão listados acima (por exemplo, `GET /Field`,\n> campanhas, envio transacional, envio SMS, modelos de mensagens, informação de conta)\n> são atualmente acessíveis por qualquer token válido — ainda não têm controlo de âmbito na\n> superfície CRM v3. As APIs Email v2 e SMS aplicam adicionalmente os seus próprios\n> controlos nos endpoints de envio / modelos / contactos / conta (ver essas especificações).\n\nA rejeição por âmbito devolve **403** com\n`{ \"error\": \"insufficient_scope\", \"required_scope\": \"…\" }` (RFC 6750 §3.1).\n\n## Formato dos dados\n\n- Todos os corpos de pedido/resposta utilizam **JSON**\n- Os nomes das propriedades estão em **snake_case**\n- Os IDs de contacto são **UUIDs**\n- Os IDs de Lista, Etiqueta e Campo são **inteiros (int64)**\n\n## Paginação\n\n**Mesma regra nas três API Mailpro** (Email v2, CRM v3, SMS): **5000 itens por página,\npor predefinição e no máximo.**\n- `numberOfRecords` — itens por página. Ausente, `0` ou acima de 5000 = 5000.\n- `pageNumber` — número de página a partir de 1 (predefinição 1).\n- Ambos são aceites como parâmetro de query **ou** como cabeçalho HTTP.\n- Uma página cheia significa que pode haver mais: peça a página seguinte até receber\n  uma página incompleta.\n- Exceção: as mensagens e o histórico com o seu HTML (`includeBodyHTML=true`) devolvem 10 itens\n  por página por predefinição.\n- Uma página vazia, ou uma pesquisa sem resultados, devolve `200` com uma lista vazia `[]`.\n  `404` significa que o próprio recurso (lista, tag, segmento, campanha…) não existe.\n\n**Parâmetros desconhecidos**: são ignorados, nunca recusados. Quando um pedido contém um parâmetro que a API\nnão reconhece, a resposta inclui `X-Ignored-Parameters` (os nomes) e `X-Documentation` (uma ligação para\nesta página), para que um erro de escrita não passe despercebido.\n\n## Códigos de erro de negócio\n\nAs violações de regras de negócio devolvem HTTP **406** com um corpo JSON:\n```json\n{ \"Message\": \"Contact cannot be created. Email cannot be empty.\" }\n```\n\nRazões habituais para 406:\n- `Email cannot be empty` — o e-mail é obrigatório\n- `Email has an invalid format` — não é um endereço de e-mail válido\n- `A contact already exist with the same Email` — violação de unicidade de e-mail\n- `The email address was unsubscribed permanently` — endereço na lista negra\n- `The email address has identified has a hard bounce` — o endereço sofreu bounce\n- `The email address has identified has a spam trap` — armadilha de spam detetada\n- `At least one List is necessary` — os contactos têm de pertencer a pelo menos uma lista\n- `One or more lists have reached the contact limit` — máximo de 1 M de contactos por lista\n- `Invalid Phone Number` — erro de formato do número de telefone\n- `Invalid country code for Phone Number` — prefixo de país não suportado\n- `Length exceeded for Phone Number (12)` — número de telefone demasiado longo\n\n### Códigos de estado devolvidos\n\n| Código | Significado |\n|--------|-------------|\n| `400` | Pedido malformado (JSON inválido, mensagem acima do limite de partes SMS) |\n| `401` | Credenciais em falta, inválidas ou expiradas |\n| `402` | Créditos insuficientes para efetuar o envio |\n| `403` | Permitido pelas suas credenciais, mas recusado — conta bloqueada para o envio de SMS |\n| `404` | O objeto referenciado não existe (contacto, lista, tag, campo, campanha) |\n| `406` | Violação de uma regra de negócio de contacto (ver a lista acima) |\n| `422` | Entrada rejeitada pela validação, ou limite de conta atingido |\n\n> **Alteração:** alguns destes casos devolviam anteriormente `404`. Os créditos\n> insuficientes devolvem agora **402**, os erros de validação e os limites de\n> conta devolvem **422**, e uma conta bloqueada para SMS devolve **403**. `404`\n> é agora usado apenas para «este objeto não existe», de forma coerente com a\n> API SMS. Se a sua integração se baseia em `404` para esses casos, atualize-a\n> com os códigos acima.\n\n## Formato dos números de telefone\n\nOs números de telefone são normalizados automaticamente. Pode enviá-los em vários formatos —\nespaços, hífenes, pontos, barras, parênteses, sublinhados e espaços não separáveis\nsão removidos antes da validação. Qualquer outro carácter (letras, apóstrofos,\n`=`, `;`, etc.) torna o número inválido.\n\n| Válido                                          | Inválido                                            |\n|------------------------------------------------|----------------------------------------------------|\n| `+41794849980` — E.164 internacional           | `0612345678` — formato nacional (`0` inicial)       |\n| `0041 79 484 99 80` — prefixo 00 + espaços       | `41'794'849'80` — apóstrofos (não removidos)       |\n| `+33 6-12-34-56-78` — + com hífenes            | `tel=0041794849980` — caracteres adicionais             |\n| `41794849980` — apenas dígitos                    | `1234567` — demasiado curto (< 8 dígitos)                 |\n\n**Regras:**\n- Prefixos aceites: `+`, `00`, ou sem prefixo\n- Comprimento após normalização: **8 a 15 dígitos** (máximo E.164)\n- O primeiro dígito após remoção do prefixo tem de ser **1–9** (não `0`)\n- Separadores removidos: espaço, tabulação, hífen `-`, ponto `.`, barra `/`, parênteses `( )`, sublinhado `_`, NBSP\n- Qualquer outro carácter (letras, apóstrofos, `=`, `;`, `+` dentro do número, etc.) → rejeitado\n- Forma armazenada na nossa base de dados: apenas dígitos, sem prefixo (por exemplo, `41794849980`)\n\n**Especificidades de Contacto v3:** o campo `phone_number` de um Contacto está atualmente\nlimitado a **12 dígitos** após normalização (face aos 15 dos endpoints SMS\nautónomos). Números de países como a Alemanha (13), o Brasil (13) ou os móveis\nitalianos (12–13) poderão ser rejeitados em `/v3/Contact` com\n`Length exceeded for Phone Number (12)`. Esta restrição legada poderá ser\nrelaxada numa versão futura.\n\n## Limites\n\n| Recurso | Limite |\n|----------|-------|\n| Etiquetas por conta | 1000 |\n| Listas por conta | 1000 |\n| Campos personalizados por conta | 50 |\n| Pedidos de API mensais por conta | 50 000 (5× os créditos de e-mail da subscrição) |\n\n## Acompanhamento da entrega (emails transacionais)\n\nCada email enviado **individualmente** (`POST /Send/Mail`, a Email API v2, o relay SMTP ou uma automação) recebe um identificador. Pode ser acompanhado até o servidor do destinatário o aceitar ou recusar. As campanhas não são abrangidas aqui: utilize as estatísticas.\n\n1. **Envie**: a resposta de `POST /Send/Mail` contém `id_single_send`.\n2. **Receba o resultado** (recomendado): os eventos webhook `email.delivered` e `email.bounced` enviam o estado final para o seu servidor assim que é conhecido, sem qualquer consulta.\n3. **Ou consulte**: `GET /Send/{id}/Status` devolve o estado atual (tabela abaixo).\n\n| `status` | Significado | Final? |\n|---|---|---|\n| `queued` | Aceite, à espera de envio. | não |\n| `scheduled` | Agendado para mais tarde (`date_planned`). | não |\n| `sent` | Entregue pelos nossos servidores, ainda sem resposta do servidor do destinatário. | não |\n| `delivered` | Aceite pelo servidor do destinatário (`diag_code` = a sua resposta SMTP). | **sim** |\n| `bounced` | Recusado. `bounce_type`: `hard` = endereço inválido ou recusa definitiva; `soft` = recusa após novas tentativas (caixa cheia, servidor inacessível…). | **sim** |\n| `not_found` | Identificador desconhecido ou que não pertence à sua conta. | **sim** |\n\n**Consultar sem entrar em ciclo**\n\n- Pare de consultar assim que o estado for **final**: já não vai mudar.\n- Primeira consulta, no mínimo, **5 minutos** após o envio: `delivered` aparece cerca de 5 minutos após a entrega.\n- Depois, **no máximo uma consulta a cada 15 minutos**.\n- Se consultar um envio não final antes de 15 minutos, recebe a mesma resposta com o cabeçalho `Retry-After` (o número de segundos a aguardar), e a chamada não conta para a sua quota.\n- **No máximo 48 horas** após o envio, cada email tem um estado final: depois disso, não consulte mais.\n- `scheduled`: não é preciso consultar antes de `date_planned`.\n- **Cada consulta conta para a sua quota de pedidos à API.** Seguindo estas regras, um envio custa no máximo 192 pedidos (4 por hora durante 48 horas), e muito menos na prática, porque para no estado final. O mesmo acompanhamento por webhook custa um único evento.\n\nOs estados são conservados durante **365 dias**.\n\n## Webhooks\n\nO Mailpro™ pode notificar o seu servidor em tempo real quando ocorrem determinados eventos na sua\nconta (criação de um contacto, entrega de um e-mail, conclusão de uma campanha, etc.).\nAo estilo Stripe — o seu URL recebe um POST assinado em HMAC-SHA256 assim que o\nevento é disparado.\n\nCada evento enviado para o seu webhook conta como 1 pedido na sua quota de pedidos à API.\n\n### Como funciona\n\n1. **Subscreva** um ou mais tipos de evento via `POST /Webhook`. A resposta\n   contém um `secret` mostrado **uma única vez** (ao estilo Stripe / GitHub) — guarde-o.\n2. **O Mailpro faz POST** de uma carga útil JSON assinada para o seu URL assim que um evento corresponda.\n3. **Verifique a assinatura** no servidor com o seu `secret` (excertos abaixo).\n4. **Responda 200–299** em 10 segundos. Caso contrário, tentamos novamente com backoff\n   exponencial (10 s → 1 min → 5 min → 30 min → 2 h → 6 h → 24 h, depois desistimos).\n\n> **⏱ Atraso de ativação** — depois de criar, atualizar ou apagar um webhook, a\n> alteração demora até **10 minutos** a propagar-se pelos servidores de API do Mailpro.\n> Durante esse intervalo, os eventos podem ainda não corresponder à sua nova subscrição\n> (ou podem ainda corresponder a uma acabada de remover). Este é o tempo necessário para\n> atualizar a cache de subscrições em memória em cada instância da API.\n\n### Envelope da carga útil\n\nTodos os eventos partilham um envelope comum:\n\n```json\n{\n  \"id\":         \"evt_8f3a9b2c4d1e5f6789abcdef01234567\",\n  \"type\":       \"contact.created\",\n  \"created\":    1748194800,\n  \"apiVersion\": \"v3\",\n  \"account\":    { \"id\": 12345, \"name\": \"ACME Corp\" },\n  \"data\":       { \"id\": \"...\", \"email\": \"alice@example.com\", \"...\": \"...\" }\n}\n```\n\n| Campo | Formato | Descrição |\n|-------|--------|-------------|\n| `id` | `evt_` + 32 hex | ID único do evento — use-o como chave de idempotência do seu lado. |\n| `type` | `<domínio>.<verbo>` | Tipo canónico do evento (por exemplo, `email.delivered`, `contact.created`). |\n| `created` | Segundos Unix UTC | Incluído na assinatura HMAC. |\n| `apiVersion` | `\"v3\"` | Qual a API do Mailpro que publicou o evento. |\n| `account.id` | int | O seu ID de cliente Mailpro. |\n| `account.name` | string | O nome da sua conta (útil para depuração do lado do cliente). |\n| `data` | object | Carga útil do evento — a forma depende de `type` (ver catálogo abaixo). |\n\n### Catálogo de eventos\n\n| Evento | Gatilho | Forma de `data` |\n|-------|---------|--------------|\n| `ping` | Teste via `POST /Webhook/{id}/test` | `{ message }` |\n| `contact.created` | `POST /Contact` | `{ id, email, firstName, lastName, createdAt }` |\n| `contact.updated` | `PUT /Contact/{id}` | `{ id, email, firstName, lastName, updatedAt, changes }` |\n| `contact.deleted` | `DELETE /Contact/{id}` | `{ id, email, deletedAt }` |\n| `list.contact_added` | Contacto adicionado a uma lista | `{ listId, listName, contactId, contactEmail, addedAt }` |\n| `list.contact_removed` | Contacto removido de uma lista | `{ listId, listName, contactId, contactEmail, removedAt }` |\n| `tag.applied` | Etiqueta aplicada a contacto | `{ tag, contactId, contactEmail, appliedAt }` |\n| `tag.removed` | Etiqueta removida | `{ tag, contactId, contactEmail, removedAt }` |\n| `segment.matched` | Contacto entra num segmento | `{ segmentId, segmentName, contactId, contactEmail, matchedAt }` |\n| `email.processed` | Envio aceite pelo pipeline | `{ messageId, email, campaignId, processedAt }` |\n| `email.delivered` | Um e-mail **individual** foi aceite pelo servidor do destinatário — API v2/v3, SMTP ou Automation (**não** campanhas). Emitido a partir do fluxo de entrega do PowerMTA. | `{ messageId, email, idSending, sendingType, diagCode, deliveredAt }` |\n| `email.opened` | Pixel de seguimento acionado | `{ messageId, email, campaignId, openedAt, ip, userAgent }` |\n| `email.clicked` | Clique em ligação seguida | `{ messageId, email, campaignId, clickedAt, url, ip, userAgent }` |\n| `email.bounced` | Hard ou soft bounce | `{ messageId, email, campaignId, bouncedAt, bounceType, reason }` |\n| `email.spam_report` | Reclamação FBL | `{ messageId, email, campaignId, reportedAt }` |\n| `email.unsubscribed` | Clique no link de remoção | `{ messageId, email, campaignId, unsubscribedAt }` |\n| `email.dropped` | Recusado antes do envio | `{ messageId, email, campaignId, droppedAt, reason }` |\n| `campaign.started` | Início do envio | `{ campaignId, name, totalRecipients, startedAt }` |\n| `campaign.finished` | Todos os destinatários processados | `{ campaignId, name, totalSent, totalFailed, finishedAt }` |\n| `import.started` | Tarefa de importação iniciada | `{ importId, fileName, totalRows, startedAt }` |\n| `import.progress` | Aos 25/50/75 % | `{ importId, fileName, processedRows, totalRows, progressPct }` |\n| `import.completed` | Importação OK | `{ importId, fileName, totalRows, addedCount, updatedCount, errorCount, completedAt }` |\n| `import.failed` | Erro fatal durante a importação | `{ importId, fileName, errorMessage, failedAt }` |\n\nSubscreva todos os eventos desta API passando `[\"*\"]` no array `events`.\n\n### Assinatura HMAC\n\nCada POST transporta um cabeçalho `X-Mailpro-Signature`:\n\n```\nX-Mailpro-Signature: t=1748194800,v1=7f9ac2b6e4d1c8a3...\nX-Mailpro-Event-Id:  evt_8f3a9b2c4d1e5f6789abcdef01234567\nX-Mailpro-Event-Type: contact.created\nX-Mailpro-Attempt:   1\n```\n\nPara verificar:\n1. Extraia `t` e `v1` do cabeçalho.\n2. **Rejeite** se `|now - t| > 300` (janela de 5 minutos contra repetição).\n3. Calcule `expected = hex(HMAC_SHA256(secret, t + \".\" + raw_body))`.\n4. **Comparação em tempo constante** de `v1` com `expected` (`crypto.timingSafeEqual`,\n   `hash_equals`, `CryptographicOperations.FixedTimeEquals`) — nunca `==`.\n\n#### Node.js\n\n```js\nconst crypto = require('crypto');\n\nfunction verifyMailproSignature(rawBody, header, secret) {\n  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));\n  if (Math.abs(Date.now() / 1000 - parseInt(parts.t)) > 300) return false;\n  const expected = crypto\n    .createHmac('sha256', secret)\n    .update(parts.t + '.' + rawBody)\n    .digest('hex');\n  return crypto.timingSafeEqual(\n    Buffer.from(parts.v1, 'hex'),\n    Buffer.from(expected, 'hex'));\n}\n```\n\n#### PHP\n\n```php\nfunction verifyMailproSignature(string $rawBody, string $header, string $secret): bool {\n    parse_str(str_replace(',', '&', $header), $p);\n    if (abs(time() - (int)$p['t']) > 300) return false;\n    $expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);\n    return hash_equals($expected, $p['v1']);\n}\n```\n\n#### Python\n\n```python\nimport hmac, hashlib, time\n\ndef verify_mailpro_signature(raw_body: bytes, header: str, secret: str) -> bool:\n    parts = dict(p.split('=') for p in header.split(','))\n    t = int(parts['t'])\n    if abs(time.time() - t) > 300:\n        return False\n    expected = hmac.new(\n        secret.encode(),\n        f\"{t}.\".encode() + raw_body,\n        hashlib.sha256\n    ).hexdigest()\n    return hmac.compare_digest(expected, parts['v1'])\n```\n\n#### C#\n\n```csharp\nusing System.Security.Cryptography;\nusing System.Text;\n\nstatic bool VerifyMailproSignature(string rawBody, string header, string secret) {\n    var parts = header.Split(',')\n        .Select(p => p.Split('='))\n        .ToDictionary(a => a[0], a => a[1]);\n    var t = long.Parse(parts[\"t\"]);\n    if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return false;\n    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));\n    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes($\"{t}.{rawBody}\"));\n    var expected = Convert.ToHexString(hash).ToLowerInvariant();\n    return CryptographicOperations.FixedTimeEquals(\n        Encoding.UTF8.GetBytes(expected),\n        Encoding.UTF8.GetBytes(parts[\"v1\"]));\n}\n```\n\n### Retentativas e desativação automática\n\n| Tentativa | Atraso antes da próxima |\n|---------|----------------------|\n| 1 → 2 | 10 segundos |\n| 2 → 3 | 1 minuto |\n| 3 → 4 | 5 minutos |\n| 4 → 5 | 30 minutos |\n| 5 → 6 | 2 horas |\n| 6 → 7 | 6 horas |\n| 7 | abandonado (entrega marcada como `failed`) |\n\nApós **10 falhas consecutivas** no mesmo webhook, este é **automaticamente\ndesativado** (`isActive: false`) e recebe um e-mail de notificação. Reative-o\ncom `PUT /Webhook/{id}` assim que o seu endpoint estiver corrigido.\n\n### Sucesso = HTTP 2xx\n\nConsideramos o seu webhook bem-sucedido apenas se devolver um estado em `200–299`.\nQualquer 3xx/4xx/5xx ou um timeout (> 10 s) é considerado uma falha e desencadeia retentativas.\nSe o seu endpoint estiver permanentemente fora de serviço, devolva `410 Gone` explicitamente — as retentativas\ncontinuam a acontecer, mas a razão da falha fica mais clara no seu registo de entregas.\n\n### Limites\n\n| Limite | Valor |\n|-------|-------|\n| Webhooks por conta | 20 |\n| Eventos subscritos por webhook | 50 |\n| Tamanho máximo da carga útil | 256 KB |\n| Timeout HTTP por tentativa | 10 segundos |\n| Retenção do histórico de entregas | 30 dias |"
  },
  "servers": [
    {
      "url": "https://api.mailpro.com/v3",
      "description": "Produção"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "IdClientHeader": [],
      "ApiKeyHeader": []
    },
    {
      "IdClientQuery": [],
      "ApiKeyQuery": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Token JWT Bearer obtido a partir de `POST /token`.\nEnvie-o como: `Authorization: Bearer <token>`\n"
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "Mailpro OAuth 2.0 — a forma moderna para aplicações de terceiros e\nplataformas de integração (Zapier, Make, n8n) atuarem em nome de um\nutilizador Mailpro com tokens de acesso revogáveis e com âmbito definido.\n\n- **authorizationCode + PKCE** (S256, obrigatório para clientes públicos) — o\n  fluxo de consentimento \"Connect Mailpro\". Emite um token de acesso + um\n  token de atualização rotativo.\n- **clientCredentials** — máquina-a-máquina, sem intervenção do utilizador.\n\nUm servidor de autorização para todas as APIs, na superfície neutra em\nversão `https://api.mailpro.com/oauth/` (emissor / declaração `iss`\n`https://api.mailpro.com/oauth/`). Os tokens de acesso são JWT RS256\n(verifique contra o JWKS em `https://api.mailpro.com/oauth/.well-known/jwks`);\nTTL **20 minutos**. Os tokens de atualização são válidos durante **30 dias**\n(deslizante) e são de **utilização única** (rodados em cada atualização). Os\ncódigos de autorização vivem **60 segundos**.\n\nO mesmo token de acesso OAuth é aceite pelas três APIs Mailpro\n(CRM v3, Email v2, SMS) — autentique-se uma vez, chame qualquer API; o\nacesso é regido pelos âmbitos do token, não pelo URL base da API. A\nautenticação legada (Bearer JWT / IdClient+ApiKey) continua a funcionar\nsem alterações e NÃO está sujeita a âmbitos.\n",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.mailpro.com/oauth/authorize",
            "tokenUrl": "https://api.mailpro.com/oauth/token",
            "refreshUrl": "https://api.mailpro.com/oauth/token",
            "scopes": {
              "contacts:read": "View your contacts, lists, tags, segments and custom fields.",
              "contacts:write": "Create, edit and delete your contacts, lists, tags and custom fields.",
              "email:send": "Send transactional and campaign emails on your behalf.",
              "email:read": "View delivery stats, opens, clicks and bounce history.",
              "email:templates:read": "View your email templates (subject, HTML, plain-text body).",
              "email:templates:write": "Create, edit and delete your email templates.",
              "sms:send": "Send transactional and campaign SMS messages on your behalf.",
              "sms:read": "View SMS credit balance and message history.",
              "sms:contacts:read": "View your SMS phone book.",
              "sms:contacts:write": "Create, edit and delete entries in your SMS phone book.",
              "webhooks:manage": "Subscribe to, edit and remove webhook endpoints for your account.",
              "account:read": "View your account credits, limits and subscription status."
            }
          },
          "clientCredentials": {
            "tokenUrl": "https://api.mailpro.com/oauth/token",
            "scopes": {
              "contacts:read": "View your contacts, lists, tags, segments and custom fields.",
              "contacts:write": "Create, edit and delete your contacts, lists, tags and custom fields.",
              "email:send": "Send transactional and campaign emails on your behalf.",
              "email:read": "View delivery stats, opens, clicks and bounce history.",
              "email:templates:read": "View your email templates (subject, HTML, plain-text body).",
              "email:templates:write": "Create, edit and delete your email templates.",
              "sms:send": "Send transactional and campaign SMS messages on your behalf.",
              "sms:read": "View SMS credit balance and message history.",
              "sms:contacts:read": "View your SMS phone book.",
              "sms:contacts:write": "Create, edit and delete entries in your SMS phone book.",
              "webhooks:manage": "Subscribe to, edit and remove webhook endpoints for your account.",
              "account:read": "View your account credits, limits and subscription status."
            }
          }
        }
      },
      "IdClientHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "IdClient",
        "description": "O seu ID de cliente Mailpro numérico — transporte preferencial (cabeçalho HTTP).\nOs URLs são registados pelos servidores web, pela Cloudflare e pelos proxies,\npelo que as credenciais em parâmetros de consulta podem fugir para registos\nde acesso. Os cabeçalhos não são registados.\n"
      },
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "ApiKey",
        "description": "A sua chave de API (GUID) — transporte preferencial (cabeçalho HTTP)."
      },
      "IdClientQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "IdClient",
        "description": "O seu ID de cliente Mailpro numérico — legado, mantido por compatibilidade retroativa.\nMude para o cabeçalho `IdClient` quando puder.\n"
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "ApiKey",
        "description": "A sua chave de API (GUID) — legado, mantido por compatibilidade retroativa."
      }
    },
    "schemas": {
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Token de acesso JWT — utilize em todos os pedidos subsequentes como `Authorization: Bearer <token>`"
          },
          "token_type": {
            "type": "string",
            "example": "bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Tempo de vida do token em segundos",
            "example": 31535999
          },
          "userName": {
            "type": "string",
            "description": "ID de cliente do utilizador autenticado",
            "example": "12345"
          },
          "refresh_token": {
            "type": "string",
            "description": "Token para obter um novo token de acesso sem voltar a autenticar"
          },
          "application": {
            "type": "string",
            "description": "Nome da integração (apenas para concessões de terceiros)",
            "nullable": true
          }
        }
      },
      "Contact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador único do contacto (UUID)",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "email": {
            "type": "string",
            "description": "Endereço de email do contacto",
            "example": "alice@example.com"
          },
          "phone_number": {
            "type": "string",
            "description": "Número de telefone — aceita `+41…`, `0041…` ou `41…` com ou sem separadores. Consulte **Formato do número de telefone** na descrição da API.",
            "example": "+41791234567"
          },
          "tags": {
            "type": "array",
            "description": "Etiquetas atribuídas a este contacto (incluídas quando `includeTags=true`)",
            "items": {
              "$ref": "#/components/schemas/TagRef"
            }
          },
          "lists": {
            "type": "array",
            "description": "Listas a que este contacto pertence (incluídas quando `includeLists=true`)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            }
          },
          "custom_fields": {
            "type": "array",
            "description": "Valores de campos personalizados para este contacto",
            "items": {
              "$ref": "#/components/schemas/FieldValue"
            }
          }
        }
      },
      "ContactEntry": {
        "type": "object",
        "required": [
          "email",
          "lists"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "Endereço de email do contacto (obrigatório, tem de ser único)",
            "example": "alice@example.com"
          },
          "phone_number": {
            "type": "string",
            "description": "Número de telefone — aceita `+41…`, `0041…` ou `41…` com ou sem separadores.\nDeve começar por um código de país válido. Máximo de 12 dígitos no Contact (consulte\n**Formato do número de telefone** na descrição da API para as regras completas).\n",
            "example": "+41791234567"
          },
          "lists": {
            "type": "array",
            "description": "Listas às quais atribuir este contacto. **É obrigatória pelo menos uma lista.**",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "tags": {
            "type": "array",
            "description": "Etiquetas a atribuir a este contacto (opcional)",
            "items": {
              "$ref": "#/components/schemas/TagRef"
            },
            "example": [
              {
                "id": 5
              }
            ]
          },
          "custom_fields": {
            "type": "array",
            "description": "Valores de campos personalizados para este contacto (opcional)",
            "items": {
              "$ref": "#/components/schemas/FieldValue"
            },
            "example": [
              {
                "id": 1,
                "value": "Alice"
              },
              {
                "id": 2,
                "value": "Dupont"
              }
            ]
          }
        }
      },
      "TagRef": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID da etiqueta (de `GET /Tag`)",
            "example": 5
          }
        }
      },
      "ListRef": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID da lista (de `GET /List`)",
            "example": 101
          }
        }
      },
      "FieldValue": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID do campo personalizado (de `GET /Field`)",
            "example": 1
          },
          "value": {
            "type": "string",
            "description": "Valor a armazenar para este campo. O tipo deve corresponder ao tipo do campo.",
            "example": "Alice"
          }
        }
      },
      "List": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Identificador único da lista",
            "example": 101
          },
          "name": {
            "type": "string",
            "description": "Nome a apresentar da lista",
            "example": "Newsletter subscribers"
          },
          "total_contacts": {
            "type": "integer",
            "description": "Número total de contactos na lista (incluído quando `includeTotalContacts=true`)",
            "example": 4521
          },
          "date_creation": {
            "type": "string",
            "format": "date-time",
            "example": "2025-01-15T10:30:00"
          }
        }
      },
      "ListEntry": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Nome a apresentar para a lista",
            "example": "Newsletter subscribers — France"
          }
        }
      },
      "Tag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Identificador único da etiqueta",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Nome a apresentar da etiqueta",
            "example": "VIP customers"
          }
        }
      },
      "TagEntry": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Nome a apresentar para a etiqueta",
            "example": "VIP customers"
          }
        }
      },
      "Field": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Identificador único do campo personalizado",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Nome interno do campo",
            "example": "first_name"
          },
          "personalisation_key": {
            "type": "string",
            "description": "O nome da variável utilizada para personalização em modelos de email (ex.: `{{first_name}}`)",
            "example": "first_name"
          },
          "field_type": {
            "type": "string",
            "description": "Tipo de dados do campo",
            "enum": [
              "text",
              "number",
              "date"
            ],
            "example": "text"
          }
        }
      },
      "FieldEntry": {
        "type": "object",
        "required": [
          "name",
          "id_field_type",
          "personalize_key"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Nome apresentado do campo",
            "example": "First name"
          },
          "id_field_type": {
            "type": "integer",
            "description": "ID do tipo de campo (tipos disponíveis via `GET /Field/Types`):\n- `1` = texto\n- `2` = número inteiro\n- `3` = data e hora\n- `4` = sim / não\n- `12` = número decimal\n",
            "example": 1
          },
          "personalize_key": {
            "type": "string",
            "description": "Chave de variável do modelo, para inserir o campo numa mensagem",
            "example": "first_name"
          },
          "sort": {
            "type": "integer",
            "minimum": 0,
            "maximum": 255,
            "description": "Ordem de apresentação (0 a 255). Obrigatório para alterar um campo (`PUT /Field/{id}`).",
            "example": 255
          }
        }
      },
      "CampaignEntry": {
        "type": "object",
        "properties": {
          "campaign_title": {
            "type": "string",
            "description": "Nome a apresentar para a campanha",
            "example": "Spring Newsletter April 2026"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Hora de envio agendada (ISO 8601). Omita para enviar de imediato.",
            "example": "2026-04-20T09:00:00"
          }
        }
      },
      "SendEmailRequest": {
        "type": "object",
        "description": "Pedido de email transacional. Forneça **ou** `email` (endereço em linha) **ou**\n`id_contact` (contacto do CRM) — não ambos.\nForneça **ou** `body_html` (HTML em linha) **ou** `id_message` (modelo guardado) — não ambos.\n",
        "properties": {
          "id_sender_email": {
            "type": "integer",
            "description": "ID do endereço de email do remetente (de `GET /SenderEmail`). Obrigatório.",
            "example": 201
          },
          "email": {
            "type": "string",
            "description": "Endereço de email do destinatário (inline). Forneça isto **OU** `id_contact`.\nSe ambos forem fornecidos, `id_contact` tem precedência.\n\n**A parte local** (antes do `@`) aceita letras, dígitos e `. _ - +` —\nincluindo o sub-addressing RFC 5322 (`user+tag@domain`, ex. Gmail, Fastmail,\nProton). Os outros caracteres especiais permitidos pela RFC 5322\n(ex. `! # $ % & ' * / = ? ^ { | } ~`) e as partes locais entre aspas **não**\nsão aceites.\n",
            "example": "alice@example.com"
          },
          "id_contact": {
            "type": "string",
            "format": "uuid",
            "description": "ID do contacto CRM do destinatário. Forneça este **OU** `email`.\nUtilizar `id_contact` permite a personalização com os valores dos campos personalizados do contacto.\n",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "subject": {
            "type": "string",
            "description": "Assunto do email (máximo 255 caracteres). Obrigatório.",
            "example": "Your order #12345 has been shipped"
          },
          "body_html": {
            "type": "string",
            "description": "Conteúdo HTML em linha. Forneça este **OU** `id_message`.\nSe ambos forem indicados, prevalece `id_message`.\n",
            "example": "<p>Hello {{first_name}}, your order has shipped!</p>"
          },
          "id_message": {
            "type": "integer",
            "description": "ID de um modelo de mensagem guardado (de `GET /Message`).\nForneça este **OU** `body_html`.\n",
            "example": 501
          },
          "reply_to": {
            "type": "string",
            "description": "Endereço de resposta (predefinição: endereço do remetente)",
            "example": "support@yourcompany.com"
          },
          "fields": {
            "type": "array",
            "description": "Campos de personalização personalizados (utilizados apenas com `email` em linha, não com `id_contact`)",
            "items": {
              "$ref": "#/components/schemas/FieldValue"
            }
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Agendar o envio para uma hora futura. Omita para enviar de imediato.",
            "example": "2026-04-15T14:00:00"
          },
          "attachments": {
            "type": "array",
            "maxItems": 5,
            "description": "Anexos opcionais (máx. **5**). Cada item é um objeto com um `filename` e um `content` codificado em Base64. Limites: **10 MB por arquivo** e **15 MB no total**, medidos após a decodificação Base64. Tipos aceitos: PDF, Word, Excel, PowerPoint, imagens (JPG, PNG, GIF, WebP), ZIP, CSV, TXT — arquivos executáveis e scripts são rejeitados. **Cobrança:** o envio mantém seu crédito habitual, mais **1 crédito por 512 KB de anexos além dos primeiros 512 KB** (anexos ≤ 512 KB não adicionam nada).",
            "items": {
              "$ref": "#/components/schemas/EmailAttachment"
            }
          },
          "keep_in_my_files": {
            "type": "boolean",
            "default": false,
            "description": "`false` (predefinição): os anexos são usados apenas para este envio — são guardados fora da sua\nconta, não contam para a sua quota de armazenamento e são eliminados automaticamente após\n35 dias. `true`: os anexos também são guardados nos ficheiros da sua conta (pasta\n`Secure/Files/Sending/api`) e contam para a sua quota de armazenamento.\n",
            "example": false
          }
        }
      },
      "EmailAttachment": {
        "type": "object",
        "required": [
          "filename",
          "content"
        ],
        "description": "Um único anexo de arquivo de e-mail com conteúdo codificado em Base64.",
        "properties": {
          "filename": {
            "type": "string",
            "description": "Nome de arquivo original exibido ao destinatário, por ex. invoice.pdf.",
            "example": "invoice.pdf"
          },
          "content": {
            "type": "string",
            "format": "byte",
            "description": "Conteúdo do arquivo, codificado em Base64.",
            "example": "JVBERi0xLjQKJVBERi0xLjQK..."
          }
        }
      },
      "SendCampaignRequest": {
        "type": "object",
        "required": [
          "id_message",
          "id_sender_email",
          "lists",
          "campaign_title"
        ],
        "properties": {
          "id_message": {
            "type": "integer",
            "description": "ID do modelo de mensagem a utilizar (de `GET /Message`)",
            "example": 501
          },
          "id_sender_email": {
            "type": "integer",
            "description": "ID do endereço do remetente (de `GET /SenderEmail`)",
            "example": 201
          },
          "id_email_reply_to": {
            "type": "integer",
            "description": "ID do endereço de remetente para resposta (obtido em `GET /SenderEmail`). Tem de ser um dos endereços de remetente da sua conta, caso contrário o pedido é recusado. Predefinição: `id_sender_email`.",
            "example": 201
          },
          "lists": {
            "type": "array",
            "description": "Listas de contactos alvo (pelo menos uma é obrigatória)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "id_segment": {
            "type": "integer",
            "description": "Filtrar destinatários por um segmento guardado (opcional)",
            "example": 7
          },
          "campaign_title": {
            "type": "string",
            "description": "Nome a apresentar para esta campanha (máximo 150 caracteres)",
            "example": "Spring Newsletter April 2026"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Hora de envio agendada (ISO 8601). Omita para enviar de imediato.",
            "example": "2026-04-20T09:00:00"
          },
          "id_automation": {
            "type": "integer",
            "description": "Associar este envio a um fluxo de trabalho de automação (opcional)",
            "example": 3
          }
        }
      },
      "SmsSingleRequest": {
        "type": "object",
        "required": [
          "phone_number",
          "message"
        ],
        "properties": {
          "phone_number": {
            "type": "string",
            "description": "Número de telefone do destinatário — aceita `+41…`, `0041…` ou `41…` com ou sem separadores. Consulte **Formato do número de telefone** na descrição da API.",
            "example": "+41791234567"
          },
          "message": {
            "type": "string",
            "description": "Texto da mensagem SMS (máximo 160 caracteres para codificação padrão)",
            "example": "Your verification code is 9473. Valid for 10 minutes."
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Agendar o envio. Omita para enviar de imediato.",
            "example": "2026-04-15T14:00:00"
          }
        }
      },
      "SmsCampaignRequest": {
        "type": "object",
        "required": [
          "lists",
          "message"
        ],
        "properties": {
          "lists": {
            "type": "array",
            "description": "Listas de contactos alvo (utiliza o campo phone_number de cada contacto)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "message": {
            "type": "string",
            "description": "Texto da mensagem SMS",
            "example": "Flash sale today — 20% off all products. Shop at mailpro.com"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Agendar o envio. Omita para enviar de imediato.",
            "example": "2026-04-20T10:00:00"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "Message": {
            "type": "string",
            "description": "Mensagem de erro legível por humanos",
            "example": "Contact cannot be created. Email cannot be empty."
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID do webhook (Mailpro)",
            "example": 42
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Endpoint HTTPS que recebe os eventos",
            "example": "https://example.com/hooks/mailpro"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tipos de eventos subscritos",
            "example": [
              "email.delivered",
              "email.bounced",
              "contact.created"
            ]
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Descrição livre (a sua própria referência)",
            "example": "Slack integration"
          },
          "isActive": {
            "type": "boolean",
            "description": "Quando `false`, nenhum evento é entregue. Desativado automaticamente após 10 falhas consecutivas.",
            "example": true
          },
          "consecutiveFailures": {
            "type": "integer",
            "description": "Número de tentativas de entrega falhadas consecutivas (reinicia em caso de sucesso)",
            "example": 0
          },
          "autoDisabledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Definido quando `isActive` foi alterado automaticamente para `false`"
          },
          "autoDisabledReason": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookCreate": {
        "type": "object",
        "required": [
          "url",
          "events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Endpoint HTTPS (HTTP recusado em produção)",
            "example": "https://example.com/hooks/mailpro"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "maxItems": 50,
            "description": "Lista de tipos de eventos a subscrever (consulte o catálogo acima). Utilize `[\"*\"]` para subscrever todos os eventos desta API.",
            "example": [
              "email.delivered",
              "email.bounced",
              "contact.created"
            ]
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Descrição livre para sua referência",
            "example": "Slack integration"
          }
        }
      },
      "WebhookCreateResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "Segredo HMAC. **Apresentado uma única vez aqui, nunca mais.** Armazene-o no lado do servidor — é utilizado para assinar todas as cargas úteis enviadas para o seu URL.",
                "example": "whsec_AbcD1234EfgH5678IjkL9012MnoP3456QrsT"
              }
            }
          }
        ]
      },
      "WebhookUpdate": {
        "type": "object",
        "description": "Atualização parcial — todos os campos são opcionais.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "maxItems": 50
          },
          "description": {
            "type": "string",
            "maxLength": 500
          },
          "isActive": {
            "type": "boolean",
            "description": "Defina `true` para reativar um webhook desativado automaticamente assim que o seu endpoint estiver corrigido."
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID da tentativa de entrega"
          },
          "eventId": {
            "type": "string",
            "description": "UUID do evento (= o campo `id` na carga útil que o seu endpoint recebeu)",
            "example": "evt_8f3a9b2c4d1e5f6789abcdef01234567"
          },
          "eventType": {
            "type": "string",
            "example": "email.delivered"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "success",
              "retrying",
              "failed"
            ],
            "description": "Estado atual desta entrega"
          },
          "statusCode": {
            "type": "integer",
            "nullable": true,
            "description": "Código de estado HTTP recebido do seu endpoint (`null` se ainda não foi tentado ou em caso de tempo esgotado)",
            "example": 200
          },
          "responseTimeMs": {
            "type": "integer",
            "nullable": true,
            "example": 142
          },
          "attemptCount": {
            "type": "integer",
            "description": "Número de tentativas até ao momento (1–7, depois falhado permanentemente)"
          },
          "lastError": {
            "type": "string",
            "nullable": true,
            "description": "Primeiros 2 KB do corpo da resposta ou mensagem de exceção quando uma tentativa falha"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "nextAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Quando a próxima nova tentativa irá ocorrer — `null` se a entrega teve sucesso ou foi abandonada permanentemente."
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Authentication",
      "description": "Obtenha um token JWT Bearer para autenticar todas as outras chamadas à API.\nO endpoint de token aceita vários tipos de concessão: `password`, `refresh_token`,\n`client_credentials` e integrações de terceiros (`zapier`, `automateio`, `microsoft-flow`).\n"
    },
    {
      "name": "Account",
      "description": "Obtenha o perfil da conta, saldo de créditos e detalhes da subscrição."
    },
    {
      "name": "Contact",
      "description": "CRUD completo para contactos CRM. Os contactos armazenam um endereço de email, um número de telefone opcional,\nvalores de campos personalizados e pertencem a uma ou mais **Listas**. Podem também ser rotulados com **Etiquetas**.\nOs IDs de contacto são UUID.\n"
    },
    {
      "name": "List",
      "description": "Gerir listas de contactos. As listas agrupam contactos para campanhas e segmentação.\nMáximo de 1.000 listas por conta (mesmo limite nas três API Mailpro e na app). Máximo de 1.000.000 contactos por lista.\n"
    },
    {
      "name": "Tag",
      "description": "Gerir etiquetas para rotulagem e segmentação de contactos.\nAs etiquetas são rótulos planos (ex.: \"VIP\", \"Churned\", \"B2B\").\nMáximo de 1.000 etiquetas por conta.\n"
    },
    {
      "name": "Field",
      "description": "Gerir campos personalizados de contacto. Os campos permitem armazenar dados adicionais em cada contacto\n(ex.: primeiro nome, cidade, nível de fidelização). Máximo de 50 campos por conta.\nTipos de campo: `text`, `number`, `date`.\n"
    },
    {
      "name": "Message",
      "description": "Gerir modelos de email HTML. Os modelos armazenam o assunto, corpo HTML\ne definições de ligação de cancelamento de subscrição. Reutilize modelos em várias campanhas.\n"
    },
    {
      "name": "Campaign",
      "description": "Consultar estatísticas de campanhas e gerir campanhas agendadas.\nAs campanhas podem ser editadas ou eliminadas até 5 minutos antes da hora de envio agendada.\n"
    },
    {
      "name": "Send",
      "description": "Enviar campanhas de email em massa (para listas de contactos) e emails transacionais individuais.\nAs campanhas podem ser agendadas ou enviadas de imediato.\n"
    },
    {
      "name": "Sender Addresses",
      "description": "Gerir endereços de email de remetente validados. Apenas endereços verificados podem ser utilizados como `From`.\n"
    },
    {
      "name": "History",
      "description": "Navegar pelo registo de envios anteriores de email e SMS."
    },
    {
      "name": "Segment",
      "description": "Gerir segmentos dinâmicos — regras de filtragem guardadas que selecionam automaticamente contactos\ncom base em valores de campo, comportamento ou pertença a listas.\n"
    },
    {
      "name": "SMS",
      "description": "Enviar mensagens SMS para contactos individuais ou para listas inteiras de contactos.\nUtiliza o campo `phone_number` de cada contacto.\n"
    },
    {
      "name": "Folders",
      "description": "Organizar modelos de mensagens em pastas."
    },
    {
      "name": "Import",
      "description": "Carregar um ficheiro CSV ou XLSX para importar contactos em massa para uma lista.\nO ficheiro é colocado em fila para processamento assíncrono — acompanhe a conclusão através\ndo `import_job_id` devolvido ou através do callback opcional `webhook_url`.\n"
    },
    {
      "name": "Webhooks",
      "description": "Subscreva o seu servidor a notificações de eventos em tempo real. O Mailpro envia uma\ncarga útil JSON assinada com HMAC via POST para o seu URL assim que o evento subscrito ocorrer.\nConsulte a secção \"Webhooks\" na introdução para o catálogo completo de eventos,\nenvelope de carga útil, fragmentos de verificação de assinatura e política de novas tentativas.\n"
    },
    {
      "name": "Suppression",
      "description": "Bounces e cancelamentos de subscrição da conta: listar, adicionar, remover."
    }
  ],
  "paths": {
    "/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "getAccessToken",
        "summary": "Obter token de acesso",
        "security": [],
        "description": "Obtém um token JWT Bearer utilizando credenciais de cliente.\nEnvie o `access_token` devolvido como `Authorization: Bearer <token>` em todos os pedidos subsequentes.\n\n### Tipos de concessão suportados\n\n| Tipo de concessão | Caso de utilização |\n|------------|----------|\n| `password` | Integração padrão — nome de utilizador + chave de API |\n| `refresh_token` | Renovar um token de acesso a expirar sem voltar a autenticar |\n| `client_credentials` | Servidor para servidor apenas com ID de cliente |\n| `zapier` / `automateio` / `microsoft-flow` | Integrações com plataformas de terceiros |\n\n### Tempo de vida do token\n- Token de acesso: **30 dias**\n- Token de atualização: **90 dias**\n\nUtilize `grant_type=refresh_token` para obter um novo token de acesso sem\nvoltar a autenticar — o seu SDK deve fazer isto automaticamente quando o\ntoken de acesso se aproximar da expiração (estratégia típica: atualizar quando\nfaltarem menos de 24 h).\n\n### Armazenar tokens com segurança\nMesmo com um tempo de vida de 30 dias, quem obtiver um `access_token` pode\nfazer-se passar pela sua conta até este expirar. Armazene os tokens num\ncofre de segredos no lado do servidor (Vault, AWS Secrets Manager, Azure Key Vault,\nvariáveis de ambiente num anfitrião reforçado). **Nunca** incorpore um token em:\n- JavaScript do lado do cliente ou em qualquer pacote para o navegador\n- Aplicações móveis distribuídas a utilizadores finais (o binário pode ser inspecionado)\n- Repositórios Git públicos ou registos de build de CI\nSe um token for divulgado, revogue-o através das definições da sua conta Mailpro\ne solicite um novo.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "description": "Tipo de concessão de autenticação",
                    "enum": [
                      "password",
                      "refresh_token",
                      "client_credentials",
                      "zapier",
                      "automateio",
                      "microsoft-flow"
                    ],
                    "example": "password"
                  },
                  "username": {
                    "type": "string",
                    "description": "O seu nome de utilizador Mailpro (email). Obrigatório para a concessão `password`.",
                    "example": "user@yourcompany.com"
                  },
                  "password": {
                    "type": "string",
                    "description": "A sua chave de API Mailpro (formato GUID). Obrigatória para a concessão `password`.",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "refresh_token": {
                    "type": "string",
                    "description": "Token de atualização anterior. Obrigatório para a concessão `refresh_token`."
                  },
                  "client_id": {
                    "type": "string",
                    "description": "O seu ID de cliente Mailpro (inteiro). Obrigatório para a concessão `client_credentials`.",
                    "example": "12345"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token emitido com sucesso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                },
                "example": {
                  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                  "token_type": "bearer",
                  "expires_in": 31535999,
                  "userName": "12345",
                  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
                  "application": null
                }
              }
            }
          },
          "400": {
            "description": "Credenciais inválidas ou tipo de concessão não suportado",
            "content": {
              "application/json": {
                "examples": {
                  "invalid_grant": {
                    "summary": "Credenciais incorretas",
                    "value": {
                      "error": "invalid_grant",
                      "error_description": "The username or password is incorrect."
                    }
                  },
                  "no_api_access": {
                    "summary": "Sem acesso à API",
                    "value": {
                      "error": "no_api_access",
                      "error_description": "The account doesn't have API access."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/Account/Me": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountMe",
        "summary": "Obter detalhes da conta",
        "description": "Devolve os detalhes do perfil e as definições da conta Mailpro autenticada.",
        "responses": {
          "200": {
            "description": "Detalhes da conta devolvidos com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Account/Credits": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountCredits",
        "summary": "Obter saldo de créditos",
        "description": "Devolve o saldo de créditos restante de email e SMS para a conta.",
        "responses": {
          "200": {
            "description": "Saldo de créditos devolvido com sucesso",
            "content": {
              "application/json": {
                "example": {
                  "email_credits": 48500,
                  "sms_credits": 1200
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Account/SubscriptionEndDate": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmSubscriptionEndDate",
        "summary": "Obter data de fim da subscrição",
        "description": "Devolve a data de expiração da subscrição Mailpro atual.",
        "responses": {
          "200": {
            "description": "Data de fim da subscrição devolvida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Account/Limits": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountLimits",
        "summary": "Obter quota mensal da API",
        "description": "Devolve a quota mensal de pedidos da API para o cliente autenticado.\n\n**Cálculo da quota:** o limite é calibrado a partir dos créditos da\nsubscrição de email (tipicamente `subscription_credits × 5`). Para\num plano de 10.000 emails/mês, isto resulta num orçamento mensal de\n50.000 pedidos. O mapeamento exato é definido pela tarefa de\nfaturação no momento da subscrição e reposto na **data de aniversário\nda subscrição** (não no dia 1 do mês civil).\n\n**O que consome créditos:**\n- Cada pedido autenticado à API para endpoints `v2`, `v3` ou `sms`\n  (cada chamada = 1 crédito, independentemente do resultado — tanto 2xx\n  como a maioria dos erros de negócio 4xx contam).\n- Cada evento de webhook despachado para um endpoint subscrito\n  (ex.: `contact.created` disparado para o seu webhook subscrito = 1 crédito\n  para além da chamada à API que o desencadeou).\n\n**O que é GRATUITO (não consome créditos):**\n- Endpoints `/health` (monitorização).\n- Pedidos que falham com `401` (falha de autenticação), `403` (proibido) ou\n  `429` (limitação de taxa).\n- Eventos de webhook para clientes sem subscrição correspondente\n  (curto-circuito em cache — sem impacto na quota).\n",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "LegacyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Instantâneo da quota",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "monthly_limit": {
                      "type": "integer",
                      "description": "Total de créditos atribuídos para o mês de faturação atual.",
                      "example": 50000
                    },
                    "remaining": {
                      "type": "integer",
                      "description": "Créditos ainda disponíveis antes da próxima reposição.",
                      "example": 32847
                    },
                    "used": {
                      "type": "integer",
                      "description": "Créditos consumidos desde a última reposição (max(0, monthly_limit - remaining)).",
                      "example": 17153
                    },
                    "is_free": {
                      "type": "boolean",
                      "description": "Verdadeiro para contas do nível gratuito.",
                      "example": false
                    },
                    "remained_sends_for_free": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Contador específico do nível gratuito. `null` para contas pagas.",
                      "example": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Não foi encontrado nenhum registo de quota para esta conta (conta recém-criada que nunca efetuou uma chamada à API; a linha será criada no primeiro pedido autenticado)."
          }
        }
      }
    },
    "/Contact": {
      "post": {
        "tags": [
          "Contact"
        ],
        "operationId": "createContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Criar um contacto",
        "description": "Cria um novo contacto CRM com campos personalizados, atribuições de lista e etiquetas opcionais.\n\n**Obrigatório:**\n- `email` — tem de ser único em toda a conta\n- `lists` — pelo menos uma atribuição de lista é obrigatória\n\n**Números de telefone** aceitam `+41…`, `0041…` ou `41…` com ou sem separadores\n(máximo 12 dígitos no Contact — consulte **Formato do número de telefone** na descrição da API).\n\n**Campos personalizados** são identificados pelo seu `id` (de `GET /Field`).\nOs valores são validados em função do tipo do campo.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactEntry"
              },
              "example": {
                "email": "alice@example.com",
                "phone_number": "+41791234567",
                "lists": [
                  {
                    "id": 101
                  }
                ],
                "tags": [
                  {
                    "id": 5
                  }
                ],
                "custom_fields": [
                  {
                    "id": 1,
                    "value": "Alice"
                  },
                  {
                    "id": 2,
                    "value": "Dupont"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contacto criado com sucesso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "description": "Formato de email inválido"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "406": {
            "description": "Violação de regra de negócio (consulte a descrição para a lista completa)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "email_exists": {
                    "summary": "Email duplicado",
                    "value": {
                      "Message": "Contact cannot be created. A contact already exist with the same Email"
                    }
                  },
                  "no_list": {
                    "summary": "Nenhuma lista indicada",
                    "value": {
                      "Message": "Contact cannot be created. At least one List is necessary"
                    }
                  },
                  "unsubscribed_forever": {
                    "summary": "Cancelamento de subscrição permanente",
                    "value": {
                      "Message": "Contact cannot be created. The email address was unsubscribed permanently"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/Contact/Multiple": {
      "post": {
        "tags": [
          "Contact"
        ],
        "operationId": "createContactsBulk",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Importar contactos (em massa)",
        "description": "Cria até **1.000 contactos** num único pedido para uma importação em massa eficiente.\nA resposta inclui uma lista de contactos criados com sucesso e uma lista de erros\npara contactos que não puderam ser criados (ex.: email duplicado, dados inválidos).\n\n**Nota:** as listas e etiquetas utilizadas em importações em massa **não** podem estar associadas\na fluxos de trabalho de automação ativos. Se estiverem, toda a importação é rejeitada com HTTP 406.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ContactEntry"
                },
                "maxItems": 1000
              },
              "example": [
                {
                  "email": "alice@example.com",
                  "lists": [
                    {
                      "id": 101
                    }
                  ],
                  "custom_fields": [
                    {
                      "id": 1,
                      "value": "Alice"
                    }
                  ]
                },
                {
                  "email": "bob@example.com",
                  "lists": [
                    {
                      "id": 101
                    }
                  ],
                  "custom_fields": [
                    {
                      "id": 1,
                      "value": "Bob"
                    }
                  ]
                }
              ]
            }
          }
        },
        "responses": {
          "200": {
            "description": "Importação em massa concluída (verifique o array `errors` para falhas parciais)",
            "content": {
              "application/json": {
                "example": {
                  "Added": [
                    {
                      "Email": "alice@example.com",
                      "Id": "550e8400-e29b-41d4-a716-446655440000"
                    },
                    {
                      "Email": "bob@example.com",
                      "Id": "660e8400-e29b-41d4-a716-446655440001"
                    }
                  ],
                  "Errors": []
                }
              }
            }
          },
          "400": {
            "description": "Entrada inválida"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "406": {
            "description": "Violação de regra de negócio (ex.: conflito de automação, limite de lista atingido)"
          }
        }
      }
    },
    "/Contact/{id}": {
      "get": {
        "tags": [
          "Contact"
        ],
        "operationId": "getContact",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Obter um contacto",
        "description": "Devolve os detalhes de um contacto específico.\nUtilize `includeTags=true` para devolver também as etiquetas atribuídas ao contacto.\nUtilize `includeLists=true` para devolver também as listas a que o contacto pertence.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID do contacto (de `POST /Contact` ou `GET /List/{id}/Contact`)",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "includeTags",
            "in": "query",
            "description": "Defina como `true` para incluir as atribuições de etiquetas do contacto na resposta",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeLists",
            "in": "query",
            "description": "Defina como `true` para incluir as pertenças a listas do contacto na resposta",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto devolvido com sucesso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                },
                "example": {
                  "id": "550e8400-e29b-41d4-a716-446655440000",
                  "email": "alice@example.com",
                  "phone_number": "41791234567",
                  "custom_fields": [
                    {
                      "id": 1,
                      "value": "Alice"
                    },
                    {
                      "id": 2,
                      "value": "Dupont"
                    }
                  ],
                  "tags": [
                    {
                      "id": 5
                    }
                  ],
                  "lists": [
                    {
                      "id": 101
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Contacto não encontrado"
          }
        }
      },
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "updateContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Atualizar um contacto",
        "description": "Atualiza o email, número de telefone, campos personalizados, atribuições de listas e etiquetas de um contacto.\nO corpo do pedido substitui os dados atuais do contacto — inclua todas as listas e etiquetas\nàs quais quer que o contacto continue atribuído.\n\n**Obrigatório no corpo:** `email` e pelo menos uma entrada em `lists`.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID do contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactEntry"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contacto atualizado com sucesso",
            "content": {
              "application/json": {
                "example": {
                  "Result": "Successful operation"
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Contacto não encontrado"
          },
          "422": {
            "description": "Erro de validação (formato de email, número de telefone, incompatibilidade de tipo de valor de campo)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "deleteContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar um contacto",
        "description": "Elimina permanentemente um contacto do CRM.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID do contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto eliminado com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Contacto não encontrado"
          }
        }
      }
    },
    "/Contact/{id}/Activity": {
      "get": {
        "tags": [
          "Contact"
        ],
        "operationId": "getCrmContactActivity",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Atividade de um contacto em 90 dias",
        "description": "Emails recebidos, abertos e clicados, cliente de email preferido e últimas campanhas do contacto.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "GUID do contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Atividade devolvida",
            "content": {
              "application/json": {
                "example": {
                  "period_days": 90,
                  "email_received": 12,
                  "email_opened": 7,
                  "email_clicked": 2,
                  "favorite_email_client": "Gmail",
                  "last_campaigns": [
                    {
                      "id": 5001,
                      "name": "Spring Sale 2026",
                      "date": "2026-10-01T09:00:00"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "O contacto pedido não existe"
          }
        }
      }
    },
    "/Contact/{id}/Tag/{tagid}": {
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "addTagToContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Adicionar etiqueta ao contacto",
        "description": "Atribui uma etiqueta a um contacto. Se a etiqueta já estiver atribuída, a operação não tem efeito.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID do contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "tagid",
            "in": "path",
            "required": true,
            "description": "ID da etiqueta (de `GET /Tag`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Etiqueta atribuída com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Contacto ou etiqueta não encontrados"
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "removeTagFromContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remover etiqueta do contacto",
        "description": "Remove a atribuição de uma etiqueta a um contacto.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID do contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "tagid",
            "in": "path",
            "required": true,
            "description": "ID da etiqueta",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Etiqueta removida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Contacto ou etiqueta não encontrados"
          }
        }
      }
    },
    "/Contact/{id}/List/{listid}": {
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "addContactToList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Adicionar contacto à lista",
        "description": "Adiciona um contacto a uma lista específica.\nDevolve HTTP 404 se o contacto tiver previamente cancelado a subscrição desta lista.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID do contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "listid",
            "in": "path",
            "required": true,
            "description": "ID da lista (de `GET /List`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto adicionado à lista com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Contacto, lista não encontrados, ou contacto cancelou a subscrição desta lista"
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "removeContactFromList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remover contacto da lista",
        "description": "Remove um contacto de uma lista específica. O próprio contacto não é eliminado.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID do contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "listid",
            "in": "path",
            "required": true,
            "description": "ID da lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto removido da lista com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Contacto ou lista não encontrados"
          }
        }
      }
    },
    "/List": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getLists",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar listas de contactos",
        "description": "Devolve todas as listas de contactos da conta.\nFiltre por nome ou data de criação. Ordene alfabeticamente com `sortByName=true`.\nInclua a contagem total de contactos por lista com `includeTotalContacts=true`.\n",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar pelo nome da lista (correspondência parcial)",
            "schema": {
              "type": "string"
            },
            "example": "newsletter"
          },
          {
            "name": "dateCreationFrom",
            "in": "query",
            "description": "Filtrar por data de criação (a partir de, ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateCreationTo",
            "in": "query",
            "description": "Filtrar por data de criação (até, ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "description": "Itens por página — 5000 por predefinição e no máximo",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "description": "Número de página (com base em 1)",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "sortByName",
            "in": "query",
            "description": "Defina como `true` para ordenar alfabeticamente pelo nome",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeTotalContacts",
            "in": "query",
            "description": "Defina como `true` para incluir a contagem `total_contacts` em cada resultado",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Listas devolvidas com sucesso",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 101,
                    "name": "Newsletter subscribers",
                    "total_contacts": 4521,
                    "date_creation": "2025-01-15T10:30:00"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      },
      "post": {
        "tags": [
          "List"
        ],
        "operationId": "createList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Criar uma lista de contactos",
        "description": "Cria uma nova lista de contactos. Máximo de 1.000 listas por conta.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListEntry"
              },
              "example": {
                "name": "Spring Campaign 2026"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lista criada com sucesso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                },
                "example": {
                  "id": 102,
                  "name": "Spring Campaign 2026",
                  "date_creation": "2026-04-14T12:00:00"
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "406": {
            "description": "Limite de listas (1.000) atingido"
          }
        }
      }
    },
    "/List/{id}": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getList",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Obter uma lista de contactos",
        "description": "Devolve os detalhes de uma lista de contactos específica.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID da lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Lista devolvida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Lista não encontrada"
          }
        }
      },
      "put": {
        "tags": [
          "List"
        ],
        "operationId": "updateList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Atualizar uma lista de contactos",
        "description": "Renomeia uma lista de contactos existente.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID da lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListEntry"
              },
              "example": {
                "name": "Newsletter subscribers — Europe"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lista atualizada com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Lista não encontrada"
          }
        }
      },
      "delete": {
        "tags": [
          "List"
        ],
        "operationId": "deleteList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar uma lista de contactos",
        "description": "Elimina permanentemente uma lista de contactos.\nOs próprios contactos não são eliminados — apenas a sua pertença a esta lista é removida.\nAs listas utilizadas por fluxos de trabalho de automação ativos não podem ser eliminadas.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID da lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Lista eliminada com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Lista não encontrada"
          },
          "406": {
            "description": "A lista é utilizada por um fluxo de trabalho de automação ativo"
          }
        }
      }
    },
    "/List/{id}/Contact": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getListContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar contactos numa lista",
        "description": "Devolve os contactos que pertencem a uma lista específica.\nFiltre por email ou data de adição. Inclua etiquetas e pertenças a listas com os respetivos sinalizadores.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID da lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          },
          {
            "name": "email",
            "in": "query",
            "description": "Filtrar por email (correspondência parcial)",
            "schema": {
              "type": "string"
            },
            "example": "@example.com"
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "description": "Itens por página — 5000 por predefinição e no máximo",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "sortByEmail",
            "in": "query",
            "description": "Ordenar alfabeticamente por email",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeTags",
            "in": "query",
            "description": "Incluir atribuições de etiquetas nos resultados",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeLists",
            "in": "query",
            "description": "Incluir pertenças a listas nos resultados",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contactos devolvidos com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Lista não encontrada ou nenhum contacto corresponde aos critérios"
          }
        }
      }
    },
    "/List/{listId}/Unsubscribers": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getListUnsubscribers",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar cancelamentos de subscrição",
        "description": "Devolve os contactos que cancelaram a subscrição de uma lista específica.",
        "parameters": [
          {
            "name": "listId",
            "in": "path",
            "required": true,
            "description": "ID da lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelamentos de subscrição devolvidos com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Lista não encontrada"
          }
        }
      }
    },
    "/Tag": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTags",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar etiquetas",
        "description": "Devolve todas as etiquetas da conta.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar por nome (correspondência parcial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "sortByName",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Etiquetas devolvidas com sucesso",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 5,
                    "name": "VIP customers"
                  },
                  {
                    "id": 6,
                    "name": "Newsletter openers"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      },
      "post": {
        "tags": [
          "Tag"
        ],
        "operationId": "createTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Criar uma etiqueta",
        "description": "Cria uma nova etiqueta. Máximo de 1.000 etiquetas por conta.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagEntry"
              },
              "example": {
                "name": "VIP customers"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Etiqueta criada com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "406": {
            "description": "Limite de etiquetas (1.000) atingido"
          }
        }
      }
    },
    "/Tag/{id}": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTag",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Obter uma etiqueta",
        "description": "Devolve uma etiqueta específica pelo seu ID.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID da etiqueta",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Etiqueta devolvida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Etiqueta não encontrada"
          }
        }
      },
      "put": {
        "tags": [
          "Tag"
        ],
        "operationId": "updateTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Atualizar uma etiqueta",
        "description": "Renomeia uma etiqueta existente.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagEntry"
              },
              "example": {
                "name": "VIP customers — updated"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Etiqueta atualizada com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Etiqueta não encontrada"
          }
        }
      },
      "delete": {
        "tags": [
          "Tag"
        ],
        "operationId": "deleteTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar uma etiqueta",
        "description": "Elimina permanentemente uma etiqueta. A etiqueta é removida de todos os contactos.\nAs etiquetas utilizadas por fluxos de trabalho de automação ativos não podem ser eliminadas.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Etiqueta eliminada com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Etiqueta não encontrada"
          },
          "406": {
            "description": "A etiqueta é utilizada por um fluxo de trabalho de automação ativo"
          }
        }
      }
    },
    "/Tag/{id}/Contact": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTagContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar contactos com uma etiqueta",
        "description": "Devolve os contactos aos quais foi atribuída uma etiqueta específica.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID da etiqueta",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Contactos devolvidos com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Etiqueta não encontrada"
          }
        }
      }
    },
    "/Field": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "listFields",
        "summary": "Listar campos personalizados",
        "description": "Devolve todos os campos personalizados de contacto definidos para a conta.\nUtilize os valores `id` devolvidos ao criar ou atualizar contactos.\n",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar pelo nome do campo",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idFieldType",
            "in": "query",
            "description": "Filtrar pelo ID do tipo de campo",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "sortByName",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campos personalizados devolvidos com sucesso",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 1,
                    "name": "first_name",
                    "personalisation_key": "first_name",
                    "field_type": "text"
                  },
                  {
                    "id": 2,
                    "name": "last_name",
                    "personalisation_key": "last_name",
                    "field_type": "text"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      },
      "post": {
        "tags": [
          "Field"
        ],
        "operationId": "createField",
        "summary": "Criar um campo personalizado",
        "description": "Cria um novo campo personalizado de contacto.\nMáximo de 50 campos por conta.\nUtilize o `id` devolvido ao atribuir valores a contactos.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FieldEntry"
              },
              "example": {
                "name": "First name",
                "id_field_type": 1,
                "personalize_key": "first_name"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campo criado com sucesso",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Field"
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "406": {
            "description": "Limite de campos (50) atingido"
          }
        }
      }
    },
    "/Field/Types": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "listFieldTypes",
        "summary": "Listar tipos de campo",
        "description": "Devolve os identificadores de tipo de campo disponíveis para utilizar ao criar um campo personalizado.\nTipos comuns: `1` = texto, `2` = número, `3` = data.\n",
        "responses": {
          "200": {
            "description": "Tipos de campo devolvidos com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Field/{id}": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "getField",
        "summary": "Obter um campo personalizado",
        "description": "Devolve a definição e os metadados de um campo personalizado específico.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID do campo",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Campo devolvido com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Campo não encontrado"
          }
        }
      },
      "put": {
        "tags": [
          "Field"
        ],
        "operationId": "updateField",
        "summary": "Atualizar um campo personalizado",
        "description": "Atualiza o nome ou a chave de personalização de um campo personalizado.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 1
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FieldEntry"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campo atualizado com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Campo não encontrado"
          }
        }
      },
      "delete": {
        "tags": [
          "Field"
        ],
        "operationId": "deleteField",
        "summary": "Eliminar um campo personalizado",
        "description": "Elimina permanentemente um campo personalizado e remove o seu valor de todos os contactos.\nEsta ação não pode ser anulada.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Campo eliminado com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Campo não encontrado"
          }
        }
      }
    },
    "/Campaign": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "listCrmCampaigns",
        "summary": "Listar campanhas",
        "description": "Devolve uma lista paginada das campanhas de email da conta.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 20
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Campanhas devolvidas com sucesso",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 8001,
                    "title": "Spring Newsletter April 2026",
                    "date_planned": "2026-04-20T09:00:01",
                    "count_open": 1823,
                    "count_sent": 4521,
                    "count_click": 342,
                    "count_badmail": 12,
                    "count_unsubscription": 8,
                    "count_clicking_users": 289,
                    "count_complaints": 1,
                    "spam_trap": 0,
                    "deliverability_score": 98
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaign",
        "summary": "Obter uma campanha",
        "description": "Devolve a configuração e o resumo de estatísticas de uma campanha específica.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Campanha devolvida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Campanha não encontrada"
          }
        }
      },
      "put": {
        "tags": [
          "Campaign"
        ],
        "operationId": "updateCrmCampaign",
        "summary": "Atualizar uma campanha",
        "description": "Atualiza o título ou a data de envio de uma campanha agendada.\nAs campanhas só podem ser modificadas até **5 minutos antes** da hora de envio agendada.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignEntry"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campanha atualizada com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Campanha não encontrada"
          }
        }
      },
      "delete": {
        "tags": [
          "Campaign"
        ],
        "operationId": "deleteCrmCampaign",
        "summary": "Eliminar uma campanha",
        "description": "Cancela e elimina permanentemente uma campanha através do seu identificador.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Campanha eliminada com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Campanha não encontrada"
          }
        }
      }
    },
    "/Campaign/Scheduled": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "listScheduledCrmCampaigns",
        "summary": "Listar as minhas campanhas agendadas",
        "description": "Devolve as campanhas com `date_planned` no futuro que ainda não foram\ndespachadas. Ordenadas por `date_planned` ascendente.\n",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 5000,
              "maximum": 5000
            }
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de campanhas agendadas",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 5001,
                    "id_client": 12345,
                    "subject": "Spring Sale 2026",
                    "date_planned": "2026-05-01T09:00:00",
                    "title": "Spring Sale",
                    "id_campaign": 7001,
                    "id_list": 101,
                    "id_segment": null,
                    "number_email": 1250
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/Cancel": {
      "post": {
        "tags": [
          "Campaign"
        ],
        "operationId": "cancelScheduledCrmCampaign",
        "summary": "Cancelar uma campanha agendada",
        "description": "Cancela uma campanha agendada no futuro. Devolve **409 Conflict** se a\ncampanha já tiver sido despachada ou não existir.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campanha cancelada",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "cancelled": 5001
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "409": {
            "description": "Já despachada ou não encontrada"
          }
        }
      }
    },
    "/Campaign/{id}/Reschedule": {
      "patch": {
        "tags": [
          "Campaign"
        ],
        "operationId": "rescheduleCrmCampaign",
        "summary": "Reagendar uma campanha agendada",
        "description": "Atualiza a data de envio planeada de uma campanha agendada. A nova data tem de estar\nno futuro e dentro de 90 dias.\n\n**Nome do campo no corpo:** a API aceita `date_planned` (snake_case, primário),\nbem como os aliases legados `PlanDate` e `scheduled_time`. Forneça qualquer\num deles — é utilizado o primeiro que não seja nulo.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Forneça um de `date_planned`, `PlanDate` ou `scheduled_time`.",
                "properties": {
                  "date_planned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Nome do campo primário.",
                    "example": "2026-12-31T14:30:00"
                  },
                  "PlanDate": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias legado aceite por compatibilidade retroativa."
                  },
                  "scheduled_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias legado aceite por compatibilidade retroativa."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campanha reagendada",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5001,
                  "date_planned": "2026-12-31T14:30:00"
                }
              }
            }
          },
          "400": {
            "description": "Data em falta — forneça um de date_planned / PlanDate / scheduled_time"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "409": {
            "description": "Já despachada",
            "not found": null,
            "or out of range": null
          }
        }
      }
    },
    "/Campaign/{id}/ByHour": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByHour",
        "summary": "Estatísticas por hora",
        "description": "Devolve a divisão horária de aberturas e cliques da campanha.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estatísticas horárias devolvidas com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/ByDay": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByDay",
        "summary": "Estatísticas por dia",
        "description": "Devolve a divisão diária de aberturas e cliques da campanha.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estatísticas diárias devolvidas com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/Openings": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignOpenings",
        "summary": "Listar aberturas de email",
        "description": "Devolve uma lista paginada de contactos que abriram o email da campanha.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          },
          {
            "name": "email",
            "in": "query",
            "description": "Filtrar pelo email do destinatário",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de aberturas devolvida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/Clicks": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignClicks",
        "summary": "Listar cliques em ligações",
        "description": "Devolve uma lista paginada de contactos que clicaram numa ligação na campanha.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de cliques devolvida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/HardBounces": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignHardBounces",
        "summary": "Listar devoluções permanentes",
        "description": "Devolve os endereços de email que tiveram devolução permanente durante esta campanha.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de devoluções permanentes devolvida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/Links": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignLinks",
        "summary": "Listar ligações monitorizadas",
        "description": "Devolve dados de desempenho de cliques para cada ligação monitorizada na campanha.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          },
          {
            "name": "link",
            "in": "query",
            "description": "Filtrar pelo URL da ligação (correspondência parcial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Estatísticas de ligações devolvidas com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/OS": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByOS",
        "summary": "Estatísticas por SO / dispositivo",
        "description": "Devolve a divisão de aberturas e cliques por sistema operativo e tipo de dispositivo.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estatísticas de SO devolvidas com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/EmailClients": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByEmailClient",
        "summary": "Estatísticas por cliente de email",
        "description": "Devolve a divisão de aberturas por cliente de email (Gmail, Outlook, Apple Mail…).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estatísticas de cliente de email devolvidas com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Campaign/{id}/Countries": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByCountry",
        "summary": "Estatísticas por país",
        "description": "Devolve a distribuição geográfica das aberturas e cliques.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estatísticas por país devolvidas com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Send/Mail": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "sendTransactionalEmailCrm",
        "summary": "Enviar email transacional",
        "description": "Envia um email transacional pontual para um único destinatário.\n\n**Destinatário:** forneça `email` (endereço em linha) **OU** `id_contact` (contacto do CRM) — não ambos.\nUtilizar `id_contact` permite a personalização automática com os valores dos campos personalizados do contacto.\n\n**Conteúdo:** forneça `body_html` (HTML em linha) **OU** `id_message` (modelo guardado) — não ambos.\nSe ambos forem indicados, prevalece `id_message`.\n\n**Obrigatórios:** `id_sender_email`, `subject` e um de (`email` ou `id_contact`),\ne um de (`body_html` ou `id_message`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendEmailRequest"
              },
              "example": {
                "id_sender_email": 201,
                "email": "alice@example.com",
                "subject": "Your order #12345 has shipped",
                "body_html": "<p>Hello {{first_name}}, your order is on its way!</p>",
                "reply_to": "support@yourcompany.com",
                "fields": [
                  {
                    "id": 1,
                    "value": "Alice"
                  }
                ],
                "attachments": [
                  {
                    "filename": "invoice.pdf",
                    "content": "JVBERi0xLjQKJVBERi0xLjQK..."
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email enviado ou agendado com sucesso",
            "content": {
              "application/json": {
                "example": {
                  "message": "Successful operation",
                  "id_single_send": 8001
                }
              }
            }
          },
          "400": {
            "description": "Formato de email inválido ou campo obrigatório em falta"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Remetente, mensagem ou contacto não encontrados; ou destinatário cancelou a subscrição",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "no_credits": {
                    "summary": "Créditos insuficientes",
                    "value": {
                      "Message": "Not enough credit available"
                    }
                  },
                  "unsubscribed": {
                    "summary": "Destinatário cancelou subscrição",
                    "value": {
                      "Message": "Email address unsubscribed."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Erro de validação (assunto demasiado longo, corpo e mensagem ambos vazios, etc.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/Send/{id}/Status": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "getTransactionalStatusCrm",
        "summary": "Obter o estado de entrega de um e-mail transacional",
        "description": "Devolve o estado de entrega atual de um e-mail transacional enviado com `POST /Send/Mail`. Passe o `id_single_send` que essa chamada devolveu.\n\nValores possíveis: `delivered` (aceite pelo servidor do destinatário, ver `diag_code`), `bounced` (recusado — `bounce_type: hard` endereço inválido ou recusa definitiva, `soft` recusa após novas tentativas), `sent` (entregue ao servidor de correio, ainda sem resposta), `scheduled` (ainda não enviado, `date_planned` no futuro) e `queued` (à espera de envio). `delivered` e `bounced` são finais: pare de consultar. Cada email tem um estado final no máximo 48 horas após o envio; consulte no máximo a cada 15 minutos (cada chamada conta para a sua quota da API) ou utilize os webhooks `email.delivered` / `email.bounced`. Só o cliente que enviou o email pode ler o seu estado — qualquer outro id devolve 404.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "O `id_single_send` retornado por `POST /Send/Mail`.",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estado de entrega atual",
            "content": {
              "application/json": {
                "example": {
                  "id_single_send": 8001,
                  "email": "alice@example.com",
                  "status": "delivered",
                  "date_planned": "2026-04-15T14:30:00",
                  "status_date": "2026-04-15T14:30:07",
                  "diag_code": "2.0.0 (success)",
                  "bounce_type": null
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Nenhum envio transacional encontrado com este id para este cliente"
          }
        }
      }
    },
    "/Send/Campaign": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "sendCampaignCrm",
        "summary": "Enviar campanha de email",
        "description": "Envia uma campanha de email em massa para uma ou mais listas de contactos.\n\n**Obrigatório:**\n- `id_message` — o modelo a enviar (de `GET /Message`)\n- `id_sender_email` — o endereço do remetente (de `GET /SenderEmail`)\n- `lists` — pelo menos uma lista de contactos alvo\n- `campaign_title` — etiqueta para o registo de estatísticas\n\n**Segmento:** opcionalmente filtre destinatários por um segmento guardado (`id_segment`).\nO segmento filtra contactos dentro das listas especificadas.\n\n**Agendamento:** defina `date_planned` para uma data/hora futura ISO 8601.\nAs campanhas podem ser editadas ou canceladas até 5 minutos antes da hora agendada.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendCampaignRequest"
              },
              "example": {
                "id_message": 501,
                "id_sender_email": 201,
                "lists": [
                  {
                    "id": 101
                  }
                ],
                "campaign_title": "Spring Newsletter April 2026",
                "date_planned": "2026-04-20T09:00:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campanha agendada ou enviada com sucesso",
            "content": {
              "application/json": {
                "example": {
                  "Message": "Successful operation. Campaigns can be edited or deleted until 5 minutes prior to send.",
                  "IdCampaign": 8001
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "402": {
            "description": "Créditos insuficientes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Mensagem, remetente, lista ou segmento não encontrados; ou créditos insuficientes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Título da campanha vazio ou demasiado longo (máximo 150 caracteres)"
          }
        }
      }
    },
    "/Send": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "getSendWaiting",
        "summary": "Listar envios agendados",
        "description": "Devolve a lista de campanhas de email à espera de serem enviadas (agendadas, ainda não processadas).",
        "parameters": [
          {
            "name": "subjectMessage",
            "in": "query",
            "description": "Filtrar pelo assunto (correspondência parcial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Envios em espera devolvidos com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Send/{id}": {
      "delete": {
        "tags": [
          "Send"
        ],
        "operationId": "deleteSendWaiting",
        "summary": "Cancelar um envio agendado",
        "description": "Cancela e elimina um envio de email agendado que ainda não foi processado.\nNão é possível cancelar envios que estejam a ser processados atualmente.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID do envio (de `GET /Send`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 3001
          }
        ],
        "responses": {
          "200": {
            "description": "Envio agendado cancelado com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Envio agendado não encontrado"
          }
        }
      }
    },
    "/Send/Mail/Scheduled": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "listScheduledCrmMails",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar os emails individuais agendados",
        "description": "Devolve os emails individuais enviados com `POST /Send/Mail` e uma data futura, ainda não expedidos. As campanhas são listadas por `GET /Campaign/Scheduled`.",
        "responses": {
          "200": {
            "description": "Emails agendados devolvidos",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id_single_send": 880123,
                    "email": "jane@example.com",
                    "subject": "Your invoice",
                    "date_planned": "2026-12-01T09:00:00"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Send/Mail/{id}/Cancel": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "cancelCrmMail",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Cancelar um email individual agendado",
        "description": "Cancela o email enquanto não tiver sido expedido.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_single_send` devolvido por `POST /Send/Mail`",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email cancelado",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "cancelled": 880123
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "409": {
            "description": "Envio não encontrado ou já expedido"
          }
        }
      }
    },
    "/Send/Mail/{id}/Reschedule": {
      "patch": {
        "tags": [
          "Send"
        ],
        "operationId": "rescheduleCrmMail",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Reagendar um email individual agendado",
        "description": "Altera a data de envio. A nova data tem de ser futura e dentro de 90 dias. `POST` também é aceite.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_single_send` devolvido por `POST /Send/Mail`",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "date_planned"
                ],
                "properties": {
                  "date_planned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Nova data de envio (ISO 8601)",
                    "example": "2026-12-15T09:00:00"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email reagendado",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 880123,
                  "date_planned": "2026-12-15T09:00:00"
                }
              }
            }
          },
          "400": {
            "description": "Data em falta"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "409": {
            "description": "Envio não encontrado, já expedido ou data fora dos limites"
          }
        }
      }
    },
    "/SenderEmail": {
      "get": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "listCrmSenderEmails",
        "summary": "Listar endereços de remetente",
        "description": "Devolve os endereços de email de remetente validados configurados para a conta.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Endereços de remetente devolvidos com sucesso",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 201,
                    "email": "newsletter@yourcompany.com",
                    "sender_name": "Your Company Newsletter",
                    "is_validated": true
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      },
      "post": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "addCrmSenderEmail",
        "security": [
          {
            "OAuth2": [
              "email:templates:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Adicionar um endereço de remetente",
        "description": "Adiciona o endereço com os mesmos controlos da aplicação e envia-lhe um email de validação. Pode ser utilizado depois de clicar na hiperligação desse email. Uma conta pode ter até 25 remetentes, validados ou pendentes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "sender_name"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Endereço do remetente",
                    "example": "news@example.com"
                  },
                  "sender_name": {
                    "type": "string",
                    "description": "Nome apresentado aos destinatários",
                    "example": "Example News"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Endereço adicionado. `domain_has_mx` é false quando o domínio não tem registo MX.",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "confirmation_sent": true,
                  "domain_has_mx": true
                }
              }
            }
          },
          "400": {
            "description": "`email` ou `sender_name` em falta"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "422": {
            "description": "Endereço recusado: inválido, já entre os remetentes da conta, domínio de email público, nome proibido ou limite de 25 atingido. A mensagem indica o motivo."
          },
          "503": {
            "description": "Temporariamente indisponível, tente novamente"
          }
        }
      }
    },
    "/SenderEmail/{id}/ResendValidation": {
      "post": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "resendCrmSenderValidation",
        "security": [
          {
            "OAuth2": [
              "email:templates:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Enviar novamente o email de validação",
        "description": "Para um endereço ainda a aguardar validação.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_sender_email` de `GET /SenderEmail`",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email enviado novamente",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "confirmation_sent": true
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Remetente não encontrado"
          },
          "409": {
            "description": "Este remetente já está validado"
          },
          "503": {
            "description": "Temporariamente indisponível, tente novamente"
          }
        }
      }
    },
    "/SenderEmail/{id}/Authentication": {
      "get": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "getCrmSenderAuthentication",
        "security": [
          {
            "OAuth2": [
              "email:templates:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Verificar o domínio de um remetente (SPF, DKIM, DMARC, MX)",
        "description": "Verifica o domínio em direto no DNS e devolve os registos a publicar. Apenas leitura. O DKIM é ativado na aplicação: `dkim_settings_url` abre o ecrã certo.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_sender_email` de `GET /SenderEmail`",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Estado do domínio. `spf.recommended_record` é criado a partir do seu registo atual: publique-o apenas se `spf.change_needed` for true.",
            "content": {
              "application/json": {
                "example": {
                  "domain": "example.com",
                  "spf": {
                    "valid": true,
                    "record": "v=spf1 include:spf.mailpro.com -all",
                    "recommended_record": "v=spf1 include:spf.mailpro.com -all",
                    "change_needed": false
                  },
                  "dkim": {
                    "valid": true,
                    "records": [
                      {
                        "host": "key._domainkey.example.com",
                        "value": "v=DKIM1;k=rsa;p=MIIBIjANBgkq...",
                        "validated": true
                      }
                    ]
                  },
                  "dmarc": {
                    "valid": true,
                    "record": "v=DMARC1; p=quarantine"
                  },
                  "mx": {
                    "valid": true,
                    "record": "mail.example.com"
                  },
                  "dkim_settings_url": "https://app.mailpro.com/dkim/list?d=example.com"
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Remetente não encontrado"
          },
          "503": {
            "description": "Temporariamente indisponível, tente novamente"
          }
        }
      }
    },
    "/History/Email": {
      "get": {
        "tags": [
          "History"
        ],
        "operationId": "getCrmHistoryEmail",
        "summary": "Listar histórico de envios de email",
        "description": "Devolve o registo de envios de email anteriores da conta.",
        "parameters": [
          {
            "name": "subjectMessage",
            "in": "query",
            "description": "Filtrar pelo assunto (correspondência parcial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idCampaign",
            "in": "query",
            "description": "Filtrar pelo ID da campanha",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "includeBodyHTML",
            "in": "query",
            "description": "Incluir o corpo HTML completo na resposta (mais lento)",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Histórico de email devolvido com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/History/SMS": {
      "get": {
        "tags": [
          "History"
        ],
        "operationId": "getCrmHistorySms",
        "summary": "Listar histórico de envios de SMS",
        "description": "Devolve o registo de envios de SMS anteriores da conta.",
        "parameters": [
          {
            "name": "dateSend",
            "in": "query",
            "description": "Filtrar a partir desta data (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateSendEnd",
            "in": "query",
            "description": "Filtrar até esta data (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "phoneNumber",
            "in": "query",
            "description": "Filtrar pelo telefone do destinatário (parcial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "Filtro de tipo de SMS",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "statusCode",
            "in": "query",
            "description": "Filtro de código de estado DLR",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sortByStatus",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Histórico de SMS devolvido com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Suppression/Bounces": {
      "get": {
        "tags": [
          "Suppression"
        ],
        "operationId": "listCrmBounces",
        "security": [
          {
            "OAuth2": [
              "email:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar os bounces da conta",
        "description": "Os mais recentes primeiro. O total está no cabeçalho `X-Total-Count`.",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "`hard` ou `soft`",
            "schema": {
              "type": "string",
              "enum": [
                "hard",
                "soft"
              ]
            }
          },
          {
            "name": "source",
            "in": "query",
            "description": "Origem do bounce: `list`, `contact`, `api` ou `smtp`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Endereço exato",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "A partir desta data",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Até esta data",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounces devolvidos",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 5521,
                    "source": "contact",
                    "type": "hard",
                    "email": "old@example.com",
                    "date": "2026-10-12T08:30:00",
                    "diag_code": "550 5.1.1 User unknown",
                    "id_list": 101
                  }
                ]
              }
            }
          },
          "400": {
            "description": "`type` tem de ser `hard` ou `soft`"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Suppression/Bounces/{id}": {
      "delete": {
        "tags": [
          "Suppression"
        ],
        "operationId": "deleteCrmBounce",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remover um bounce",
        "description": "`source` e `type` são obrigatórios, tal como devolvidos por `GET /Suppression/Bounces`: um `id` sozinho não é único.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` do bounce",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "`list`, `contact`, `api` ou `smtp`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": true,
            "description": "`hard` ou `soft`",
            "schema": {
              "type": "string",
              "enum": [
                "hard",
                "soft"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounce removido",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5521,
                  "restored": false
                }
              }
            }
          },
          "400": {
            "description": "`source` ou `type` em falta"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Bounce não encontrado"
          }
        }
      }
    },
    "/Suppression/Bounces/{id}/Restore": {
      "post": {
        "tags": [
          "Suppression"
        ],
        "operationId": "restoreCrmBounce",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Repor nas suas listas um contacto com hard bounce",
        "description": "Apenas para um hard bounce com a origem `contact`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` do bounce",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto reposto",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5521,
                  "restored": true
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Hard bounce de um contacto não encontrado ou endereço já não ativo"
          }
        }
      }
    },
    "/Suppression/Unsubscribers": {
      "get": {
        "tags": [
          "Suppression"
        ],
        "operationId": "listCrmUnsubscribers",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar os cancelamentos de subscrição da conta",
        "description": "Os mais recentes primeiro. O total está no cabeçalho `X-Total-Count`.",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "description": "Origem do cancelamento: `contact`, `list`, `smtp`, `api` ou `api3`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Endereço exato",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "A partir desta data",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Até esta data",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelamentos de subscrição devolvidos",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 9012,
                    "source": "api3",
                    "email": "stop@example.com",
                    "date": "2026-10-20T16:05:00",
                    "list_name": null,
                    "comment": "Asked by phone"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      },
      "post": {
        "tags": [
          "Suppression"
        ],
        "operationId": "addCrmUnsubscriber",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Adicionar um cancelamento de subscrição",
        "description": "Sem `id_list`, a subscrição do endereço é cancelada para toda a conta. Com `id_list`, apenas nessa lista de cancelamento.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Endereço cuja subscrição cancelar",
                    "example": "stop@example.com"
                  },
                  "comment": {
                    "type": "string",
                    "description": "Comentário livre",
                    "example": "Asked by phone"
                  },
                  "id_list": {
                    "type": "integer",
                    "format": "int64",
                    "description": "Lista de cancelamento da conta (opcional)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Concluído. `already_unsubscribed` é true quando a subscrição do endereço já estava cancelada.",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "already_unsubscribed": false
                }
              }
            }
          },
          "400": {
            "description": "É necessário um endereço válido"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "`id_list` não é uma lista de cancelamento desta conta"
          }
        }
      }
    },
    "/Suppression/Unsubscribers/{id}": {
      "delete": {
        "tags": [
          "Suppression"
        ],
        "operationId": "deleteCrmUnsubscriber",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remover um cancelamento de subscrição",
        "description": "`source` é obrigatório, tal como devolvido por `GET /Suppression/Unsubscribers`: um `id` sozinho não é único.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` do cancelamento",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "`contact`, `list`, `smtp`, `api` ou `api3`",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelamento de subscrição removido",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "removed": 9012
                }
              }
            }
          },
          "400": {
            "description": "`source` é obrigatório"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Cancelamento de subscrição não encontrado"
          }
        }
      }
    },
    "/Segment": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "getSegments",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar segmentos",
        "description": "Devolve todos os segmentos dinâmicos guardados da conta.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Segmentos devolvidos com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Segment/{id}/Contact": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "getCrmSegmentContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar os contactos de um segmento",
        "description": "Com `id_list`, devolve apenas os contactos dessa lista que entram no segmento: os que uma campanha lista + segmento alcançaria. O total está no cabeçalho `X-Total-Count`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID do segmento",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "id_list",
            "in": "query",
            "description": "Limitar a esta lista (opcional)",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contactos devolvidos"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Segmento ou lista não encontrados"
          }
        }
      }
    },
    "/Segment/{id}/Count": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "countCrmSegmentContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Contar os contactos de um segmento",
        "description": "Apenas o número, para conhecer antecipadamente a dimensão de um envio. Com `id_list`, conta apenas os contactos dessa lista que entram no segmento.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID do segmento",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "id_list",
            "in": "query",
            "description": "Limitar a esta lista (opcional)",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Número de contactos",
            "content": {
              "application/json": {
                "example": {
                  "total": 1250
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Segmento ou lista não encontrados"
          }
        }
      }
    },
    "/Segment/{id}/contact/{contactId}/check": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "checkContactInSegment",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Verificar se um contacto corresponde a um segmento",
        "description": "Verifica se um contacto específico (por GUID) corresponde atualmente às regras\nde um determinado segmento. Útil para acionar lógica de automação a pedido.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID do segmento",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 4001
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "GUID do contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "11111111-2222-3333-4444-555555555555"
          }
        ],
        "responses": {
          "200": {
            "description": "Devolve o resultado da correspondência"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Segmento ou contacto não encontrados"
          }
        }
      }
    },
    "/FolderList": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderLists",
        "summary": "Listar pastas de listas",
        "description": "Devolve as pastas utilizadas para organizar as listas de contactos.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar pelo nome da pasta (parcial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Pastas de listas devolvidas com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/FolderList/{id}": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderListById",
        "summary": "Obter uma única pasta de listas",
        "description": "Devolve os detalhes de uma pasta de listas pelo seu ID.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID da pasta",
            "schema": {
              "type": "integer"
            },
            "example": 12
          }
        ],
        "responses": {
          "200": {
            "description": "Pasta devolvida com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Pasta não encontrada"
          }
        }
      }
    },
    "/Sms/SingleSMS": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "sendSingleSmsCrm",
        "summary": "Enviar SMS único",
        "description": "Envia um SMS único para um número de telefone diretamente.\nUtilize-o para mensagens transacionais (OTPs, confirmações de encomenda, alertas).\nO número de telefone aceita `+41…`, `0041…` ou `41…` com ou sem separadores —\nconsulte **Formato do número de telefone** na descrição da API para entradas aceites / rejeitadas.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmsSingleRequest"
              },
              "example": {
                "phone_number": "+41791234567",
                "message": "Your verification code is 9473. Valid for 10 minutes."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS enviado ou agendado com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "422": {
            "description": "Formato de número de telefone inválido"
          }
        }
      }
    },
    "/Sms/Campaign": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "sendSmsCampaignCrm",
        "summary": "Enviar campanha de SMS",
        "description": "Envia uma mensagem SMS para o campo `phone_number` de todos os contactos em uma ou mais listas.\nOs contactos sem número de telefone são ignorados.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmsCampaignRequest"
              },
              "example": {
                "lists": [
                  {
                    "id": 101
                  }
                ],
                "message": "Flash sale today — 20% off. Shop at mailpro.com",
                "date_planned": "2026-04-20T10:00:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campanha de SMS enviada ou agendada com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Sms": {
      "get": {
        "tags": [
          "SMS"
        ],
        "operationId": "getSmsSends",
        "summary": "Listar histórico de SMS",
        "description": "Devolve o registo de envios de SMS (único e em campanha) da conta.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Histórico de SMS devolvido com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Sms/{id}": {
      "delete": {
        "tags": [
          "SMS"
        ],
        "operationId": "deleteSmsSendCrm",
        "summary": "Cancelar um SMS agendado",
        "description": "Cancela um envio de SMS agendado pendente.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID do envio de SMS",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5001
          }
        ],
        "responses": {
          "200": {
            "description": "SMS agendado cancelado com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "404": {
            "description": "Envio de SMS não encontrado"
          }
        }
      }
    },
    "/Sms/Scheduled": {
      "get": {
        "tags": [
          "SMS"
        ],
        "operationId": "listScheduledSmsCrm",
        "summary": "Listar envios de SMS agendados",
        "description": "Devolve envios de SMS com `date_planned` futuro (ainda não despachados), ordenados por data ascendente.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 100
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Envios de SMS agendados devolvidos com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Sms/{id}/Cancel": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "cancelScheduledSmsCrm",
        "summary": "Cancelar um SMS agendado",
        "description": "Cancela um envio de SMS agendado apenas se ainda não tiver sido despachado.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID do envio de SMS",
            "schema": {
              "type": "integer"
            },
            "example": 5001
          }
        ],
        "responses": {
          "200": {
            "description": "SMS agendado cancelado com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "409": {
            "description": "Envio de SMS não encontrado, não pertence ao cliente, ou já despachado"
          }
        }
      }
    },
    "/Sms/{id}/Reschedule": {
      "patch": {
        "tags": [
          "SMS"
        ],
        "operationId": "rescheduleScheduledSmsCrm",
        "summary": "Reagendar um SMS agendado",
        "description": "Atualiza a data de envio planeada de um SMS agendado. A nova data tem de estar\nno futuro e dentro de 90 dias. Falha se o SMS já tiver sido despachado.\n\n**Nome do campo no corpo:** a API aceita `date_planned` (snake_case, primário),\nbem como os aliases legados `PlanDate` e `scheduled_time`. Forneça qualquer\num deles.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID do envio de SMS",
            "schema": {
              "type": "integer"
            },
            "example": 5001
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Forneça um de `date_planned`, `PlanDate` ou `scheduled_time`.",
                "properties": {
                  "date_planned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Nome do campo primário.",
                    "example": "2026-05-10T14:00:00Z"
                  },
                  "PlanDate": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias legado aceite por compatibilidade retroativa."
                  },
                  "scheduled_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias legado aceite por compatibilidade retroativa."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS agendado reagendado com sucesso"
          },
          "400": {
            "description": "Data em falta — forneça um de date_planned / PlanDate / scheduled_time"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "409": {
            "description": "Reagendamento falhou (não encontrado, já despachado ou data fora de intervalo)"
          }
        }
      }
    },
    "/FolderMessage": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderMessages",
        "summary": "Listar pastas de mensagens",
        "description": "Devolve as pastas utilizadas para organizar os modelos de mensagens de email.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar pelo nome da pasta",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Pastas de mensagens devolvidas com sucesso"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Import/Upload": {
      "post": {
        "tags": [
          "Import"
        ],
        "operationId": "uploadImportFile",
        "summary": "Carregar um ficheiro de contactos (CSV / XLSX) para importação em massa",
        "description": "Carregue um ficheiro CSV ou XLSX para importar contactos para uma lista.\nO ficheiro é armazenado e colocado em fila para processamento assíncrono — o endpoint\ndevolve um `import_job_id` de imediato.\n\n**Tipo de pedido:** `multipart/form-data`\n\n**Campos de formulário obrigatórios:**\n- `id_list` (inteiro) — ID da lista de destino\n- `file` (binário) — o ficheiro CSV ou XLSX a carregar\n- `json_match_fields` (string, JSON) — que coluna do ficheiro vai para\n  que campo do contacto. `email_position` = coluna do e-mail, numerada a\n  partir de 0 (obrigatório), `phone_position` = coluna do número de\n  telemóvel ou `-1`, `fields` = IDs dos restantes campos (ver\n  `GET /Field`), `fields_position` = coluna de cada um deles,\n  `update_fields` = atualizar os contactos existentes. Correspondência\n  ausente ou inválida: resposta `400`, nenhuma importação é criada.\n\n**Opcional:**\n- `webhook_url` (string) — URL HTTPS chamado quando o processamento termina.\n  Recebe uma carga útil JSON com o estado final (sucesso / falha) e\n  contagens de linhas importadas / ignoradas / inválidas.\n\n**Formato do ficheiro:**\n- Codificado em UTF-8\n- A primeira linha pode conter os cabeçalhos (deteção automática: uma\n  primeira linha sem nenhum endereço de e-mail é ignorada)\n- As colunas são associadas aos campos por `json_match_fields`\n- Tamanho máximo do ficheiro: 50 MB\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "id_list",
                  "file",
                  "json_match_fields"
                ],
                "properties": {
                  "id_list": {
                    "type": "integer",
                    "format": "int64",
                    "description": "ID da lista de destino",
                    "example": 11088
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Ficheiro CSV ou XLSX (UTF-8, máximo 50 MB)"
                  },
                  "json_match_fields": {
                    "type": "string",
                    "description": "Correspondência JSON entre as colunas do ficheiro e os campos do contacto",
                    "example": "{\"email_position\":0,\"phone_position\":-1,\"fields\":[1315597,1315598],\"fields_position\":[1,2],\"update_fields\":true}"
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL de callback invocado quando o processamento termina",
                    "example": "https://example.com/hooks/mailpro-import"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ficheiro aceite e colocado em fila",
            "content": {
              "application/json": {
                "example": {
                  "result": "OK",
                  "import_job_id": 14,
                  "internal_name": "230575-20260420142615-contacts.csv"
                }
              }
            }
          },
          "400": {
            "description": "Nenhum ficheiro carregado, id_list em falta, ou json_match_fields ausente / inválido"
          },
          "401": {
            "description": "Token inválido ou expirado"
          },
          "500": {
            "description": "Erro de servidor ou de armazenamento"
          }
        }
      }
    },
    "/Webhook": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Serviço de gestão de webhooks (separado do anfitrião principal da API)"
        }
      ],
      "get": {
        "summary": "Listar webhooks",
        "description": "Devolve todos os webhooks configurados na conta autenticada.",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Array de webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Webhook"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      },
      "post": {
        "summary": "Criar um webhook",
        "description": "Cria uma nova subscrição de webhook. A resposta contém um `secret` que\nis **apresentado apenas uma vez** — armazene-o imediatamente, no lado do servidor. Utilize-o para\nverificar a assinatura HMAC de cada carga útil recebida.\n\n**Limites**: máximo 20 webhooks por conta, máximo 50 eventos por webhook.\n",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreate"
              },
              "example": {
                "url": "https://example.com/hooks/mailpro",
                "events": [
                  "email.delivered",
                  "email.bounced",
                  "contact.created"
                ],
                "description": "Integração Slack"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook criado — `secret` é devolvido apenas aqui",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreateResponse"
                },
                "example": {
                  "id": 42,
                  "url": "https://example.com/hooks/mailpro",
                  "events": [
                    "email.delivered",
                    "email.bounced",
                    "contact.created"
                  ],
                  "description": "Integração Slack",
                  "isActive": true,
                  "consecutiveFailures": 0,
                  "autoDisabledAt": null,
                  "autoDisabledReason": null,
                  "createdAt": "2026-05-25T10:00:00Z",
                  "updatedAt": "2026-05-25T10:00:00Z",
                  "secret": "whsec_AbcD1234EfgH5678IjkL9012MnoP3456QrsT"
                }
              }
            }
          },
          "400": {
            "description": "URL, lista de eventos inválidos ou limite excedido"
          },
          "401": {
            "description": "Token inválido ou expirado"
          }
        }
      }
    },
    "/Webhook/{id}": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Serviço de gestão de webhooks"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "get": {
        "summary": "Obter um webhook",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "O webhook",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "404": {
            "description": "Webhook não encontrado"
          }
        }
      },
      "put": {
        "summary": "Atualizar um webhook",
        "description": "Atualização parcial — apenas os campos enviados são modificados.",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook atualizado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "description": "Carga útil inválida"
          },
          "404": {
            "description": "Webhook não encontrado"
          }
        }
      },
      "delete": {
        "summary": "Eliminar um webhook",
        "description": "Elimina permanentemente o webhook e todo o seu histórico de entregas.",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "204": {
            "description": "Eliminado"
          },
          "404": {
            "description": "Webhook não encontrado"
          }
        }
      }
    },
    "/Webhook/{id}/test": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Serviço de gestão de webhooks"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "post": {
        "summary": "Enviar um evento de teste",
        "description": "Coloca em fila um evento `ping` imediato (ou qualquer outro tipo de evento através do\ncampo `eventType` do corpo) para o seu endpoint. Útil para verificar a acessibilidade\ne a verificação de assinatura antes de depender de eventos reais.\n",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "eventType": {
                    "type": "string",
                    "default": "ping",
                    "description": "Substituir o tipo de evento (deve estar subscrito por este webhook). `ping` funciona sempre."
                  }
                }
              },
              "example": {
                "eventType": "ping"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Evento colocado em fila para despacho",
            "content": {
              "application/json": {
                "example": {
                  "deliveryId": 1234,
                  "eventId": "evt_8f3a9b2c4d1e5f6789abcdef01234567",
                  "eventType": "ping"
                }
              }
            }
          },
          "404": {
            "description": "Webhook não encontrado"
          }
        }
      }
    },
    "/Webhook/{id}/rotate-secret": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Serviço de gestão de webhooks"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "post": {
        "summary": "Rodar o segredo HMAC",
        "description": "Gera um novo segredo HMAC e invalida o antigo de imediato.\nO novo segredo é devolvido **uma única vez** na resposta — armazene-o. Cada\nentrega a partir deste momento será assinada com o novo segredo.\n\nUtilize isto se suspeitar que o seu segredo foi comprometido.\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Novo segredo (visível uma vez)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "secret": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "secret": "whsec_XyzA9876BcdE5432FghI1098JklM7654NopQ"
                }
              }
            }
          },
          "404": {
            "description": "Webhook não encontrado"
          }
        }
      }
    },
    "/Webhook/{id}/deliveries": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Serviço de gestão de webhooks"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        },
        {
          "in": "query",
          "name": "limit",
          "schema": {
            "type": "integer",
            "default": 50,
            "maximum": 200
          }
        },
        {
          "in": "query",
          "name": "cursor",
          "schema": {
            "type": "integer",
            "format": "int64"
          },
          "description": "Cursor de paginação (= último ID de entrega visto). Devolve entregas com `id < cursor`."
        }
      ],
      "get": {
        "summary": "Listar tentativas de entrega",
        "description": "Devolve o histórico de tentativas de despacho deste webhook (retenção de 30 dias).\nÚtil para depuração — quais eventos foram enviados, códigos de estado recebidos,\ncontagens de novas tentativas, mensagens de erro.\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Array de tentativas de entrega (mais recentes primeiro)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Webhook não encontrado"
          }
        }
      }
    },
    "/Webhook/{id}/deliveries/{deliveryId}/replay": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Serviço de gestão de webhooks"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        },
        {
          "in": "path",
          "name": "deliveryId",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "post": {
        "summary": "Reproduzir uma entrega",
        "description": "Coloca em fila uma nova tentativa de entrega com a mesma carga útil do original.\nÚtil para reacionar uma entrega falhada após corrigir o seu endpoint.\nUma NOVA linha `Webhook_Delivery` é criada (a original é preservada\npara auditoria). A reprodução passa pela política de novas tentativas padrão\n(10s → 1min → 5min → 30min → 2h → 6h → 24h, depois abandonada).\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "202": {
            "description": "Reprodução colocada em fila",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "new_delivery_id": {
                      "type": "integer",
                      "format": "int64",
                      "example": 12999
                    },
                    "queued_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Webhook ou entrega não encontrados"
          }
        }
      }
    }
  }
}